/* * Engine Alpha ist eine anfängerorientierte 2D-Gaming Engine. * * Copyright (c) 2011 - 2014 Michael Andonie and contributors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ea.edu; import ea.*; import ea.internal.util.Logger; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Random; /** * Die Anzeige ermöglicht Punktedarstellung im EDU-Konzept.<br /> Zusätzlich realisiert sie das * <b>Interfacefreie</b> Reagieren auf Tastendruck und Ticken. Dies jedoch wird einem Schüler, der * nach dem EDU-Konzept lernt so nie vorkommen. Für den Lerneffekt wird die Funktionalität in den * paketexternen Klassen auf spezielle Interfaces beschränkt. * * @author Michael Andonie */ public class AnzeigeE extends Manager implements Ticker, TastenReagierbar, KlickReagierbar, RechtsKlickReagierbar { private static final long serialVersionUID = 3387123025090347796L; /** * Der Linke (Punkte-)Text. */ private final Text links; /** * Der rechte (Punkte-)Text. */ private final Text rechts; /** * Der Text für den Bindestrich */ private final Text strich; /** * Die Liste aller TICKER-Aufgaben */ private final ArrayList<Auftrag> aufgabenT; /** * Die Liste aller TASTEN-Aufgaben */ private final ArrayList<TastenAuftrag> aufgaben; /** * Die Liste aller KLICK-Aufgaben */ private final ArrayList<KlickAuftrag> aufgabenKlick; /** * Zufallsgenerator */ private final Random random = new Random(); /** * Die aktuelle Runde des Tickers */ private int runde = 0; /** * Die Maus des Fensters. Erscheint <b>automatisch</b>, sobald der erste Listener angemeldet * wird. */ private Maus maus; /** * Konstruktor. Erstellt die Texte fuer Links- und Rechtspunkte. * * @param breite * Die gewünschte Breite der Anzeige in Pixel. * @param hoehe * Die gewünschtte Höhe der Anzeige in Pixel. */ public AnzeigeE (int breite, int hoehe) { links = new Text(0, 10, "0"); rechts = new Text(0, 10, "0"); aufgaben = new ArrayList<>(); aufgabenT = new ArrayList<>(); aufgabenKlick = new ArrayList<>(); FensterE.getFenster(breite, hoehe).wurzel.add(links, rechts, strich = new Text(0, 10, "-")); FensterE.getFenster(breite, hoehe).tastenReagierbarAnmelden(this); super.anmelden(this, 1); punkteAnzeigen(false); punkteAlignen(); } /** * Schickt einen einfachen <i>Ja / Nein - Dialog</i> an den Nutzer. * @param text Der Text für den Dialog. * @return <code>true</code>, wenn der User auf "Ja" drückt, sonst <code>false</code>. */ public static boolean frage(String text) { return FensterE.getFenster().frage(text); } /** * Öffnet einen Eingabedialog (Text) für den Nutzer. * @param nachricht der Text für den Dialog. * @return Die Nutzereingabe als String. Hat der Nutzer abgebrochen, ist dieser Wert <code>null</code>. */ public static String eingabeFordern(String nachricht) { return FensterE.getFenster().eingabeFordern(nachricht); } /** * Schickt einen einseitigen Nachrichtendialog an den Nutzer. * @param nachricht Der Text für die Nachricht. */ public static void nachrichtDialog(String nachricht) { FensterE.getFenster().nachrichtSchicken(nachricht); } /** * Erstellt eine (sehr rudimentäre) Highscore-Anzeige. * @param namen Die Nutzernamen (0: bester -> schlechtester Spieler) * @param punkte Die zugehörigen Punkte (analog zu den Namen: 0 -> schlechtester) * @param festertitel Der Titel des Dialogfensters mit den Highscores. */ public static void highscoreAnzeigen(String[] namen, int[] punkte, String festertitel) { FensterE.getFenster().highscoreAnzeigen(namen, punkte, festertitel); } /** * Stellt, ob das Hilfs-Raster, das die Koordinatenachsen visualisiert, dargestellt werden soll. * @param sichtbar ist dieser Wert <code>true</code>, wird das Raster dargestellt. Ist er <code>false</code>, * wird das Raster deaktiviert. */ public void rasterSichtbarSetzen(boolean sichtbar) { EngineAlpha.setDebug(sichtbar); } /** * Setzt, ob die Punkte angezeigt werden sollen. * * @param visible * Bei <code>true</code> ist die Punkteanzeige sichtbar, bei <code>false</code> ist sie * unsichtbar. */ public void punkteAnzeigen (boolean visible) { rechts.sichtbarSetzen(visible); links.sichtbarSetzen(visible); strich.sichtbarSetzen(visible); } /** * Interne Align-Methode für harmonisches Aussehen der Punkte */ private void punkteAlignen () { float lLinks = links.dimension().breite; float lRechts = rechts.dimension().breite; float lStrich = strich.dimension().breite; float groesser = lLinks > lRechts ? lLinks : lRechts; float breite = FensterE.getFenster().fensterGroesse().breite; strich.positionSetzen((breite - lStrich) / 2, 10); links.positionSetzen(((breite - lStrich) / 2) - groesser - 5, 10); rechts.positionSetzen((breite + lStrich) / 2 + 5, 10); } /** * * @param farbe Die Farbe in der die Punktestaende angezeigt werden sollen */ public void setzeFarbePunktestand(String farbe) { links.setzeFarbe(farbe); rechts.setzeFarbe(farbe); strich.setzeFarbe(farbe); } public Maus getMaus () { return maus; } /** * Gibt eine Zufallszahl zurück * * @param von * untere Grenze (inklusive) * @param bis * obere Grenze (inklusive) * * @return Zufallszahl im Bereich <code>von</code> - <code>bis</code> */ public int zufallszahlVonBis (int von, int bis) { if (von > bis) { Logger.error("Die Zufallszahl von (" + von + ") war größer als die " + "Zufallszahl bis (" + bis + ")."); return -1; } return random.nextInt(bis - von + 1) + von; } /** * Setzt den Punktestand auf der linken Seite. * * @param punkte * Der neue darzustellende Punktestand der linken Seite */ public void punkteLinksSetzen (int punkte) { links.inhaltSetzen("" + punkte); punkteAlignen(); } /** * Setzt, ob der Punktestand auf der linken Seite sichtbar sein soll oder nicht. <b>Nur wenn * beide Texte links und rechts sichtbar sind, ist auch der Strich in der Mitte sichtbar.</b> * * @param sichtbar * Ob der Linke Text sichtbar sein soll. */ public void punkteLinksSichtbarSetzen (boolean sichtbar) { links.sichtbarSetzen(sichtbar); strich.sichtbarSetzen(links.sichtbar() && rechts.sichtbar()); } /** * Setzt den Punktestand auf der rechten Seite. * * @param punkte * Der neue darzustellende Punktestand der rechten Seite */ public void punkteRechtsSetzen (int punkte) { rechts.inhaltSetzen("" + punkte); punkteAlignen(); } /** * Setzt, ob der Punktestand auf der rechten Seite sichtbar sein soll oder nicht. <b>Nur wenn * beide Texte links und rechts sichtbar sind, ist auch der Strich in der Mitte sichtbar.</b> * * @param sichtbar * Ob der Linke Text sichtbar sein soll. */ public void punkteRechtsSichtbarSetzen (boolean sichtbar) { rechts.sichtbarSetzen(sichtbar); strich.sichtbarSetzen(links.sichtbar() && rechts.sichtbar()); } /** * Meldet ein Objekt zum Ticken an. Intern laesst sich theoretisch ein Objekt <b>JEDER</b> * Klasse anmelden!<br /> Deshalb <i>sollten nur Objekte angemeldet werden, die Instanzen des * EDU-<code>TICKER</code>-Interfaces sind!!</i> * * @param o * Das anzumeldende Objekt, dessen Tickermethode aufgerufen werden soll.<br /> Es <b>MUSS</b> * eine Methode <code>tick()</code> haben. * @param intervall * Das Intervall in Millisekunden, in dem das anzumeldende Objekt aufgerufen. * * @see Ticker * @see #tickerAbmelden(Object) */ public void tickerAnmelden (Object o, int intervall) { Class<?> klasse = o.getClass(); Method[] methoden = klasse.getMethods(); for (int i = 0; i < methoden.length; i++) { if (methoden[i].getName().equals("tick")) { aufgabenT.add(new Auftrag(o, methoden[i], intervall)); return; } } } /** * Meldet einen "Ticker" ab. * * @param o * Das Angemeldete "Ticker"-Objekt, das nun nicht mehr aufgerufen werden soll. * * @see #tickerAnmelden(Object, int) */ public void tickerAbmelden (Object o) { for (int i = aufgabenT.size() - 1; i >= 0; i--) { if (aufgabenT.get(i).client().equals(o)) { aufgabenT.remove(i); return; } } } /** * Meldet ein Objekt an, das ab sofort auf Tastendruck reagieren wird.<br /> Intern laesst sich * theoretisch ein Objekt <b>JEDER</b> Klasse anmelden!<br /> Deshalb <i>sollten nur Objekte * angemeldet werden, die Instanzen des EDU-<code>TASTENREAGIERBARANMELDEN</code>-Interfaces * sind!!</i> * * @param o * Das anzumeldende Objekt. Dieses wird ab sofort ueber jeden Tastendruck informiert. * * @see ea.TastenReagierbar */ public void tastenReagierbarAnmelden (Object o) { Class<?> klasse = o.getClass(); Method[] methoden = klasse.getMethods(); for (int i = 0; i < methoden.length; i++) { if (methoden[i].getName().equals("tasteReagieren")) { aufgaben.add(new TastenAuftrag(o, methoden[i])); return; } } } /** * Meldet ein Objekt an, das ab sofort auf Mausklicks reagieren wird.<br /> Intern laesst sich * theoretisch ein Objekt <b>JEDER</b> Klasse anmelden!<br /> Deshalb <i>sollten nur Objekte * angemeldet werden, die Instanzen eines interfaces EDU-<code>KLICKREAGIERBAR</code>-Interfaces * sind!!</i><br /> <br /> <br /> Example:<br /> <b /> <code>KLICKREAGIERBAR { <br /> //Eine * Methode diesen Namens MUSS existieren!!<br /> public abstract void klickReagieren(int x, int * y);<br /> }</code> * * @param client * Das anzumeldende Objekt. Dieses wird ab sofort ueber jeden Mausklick informiert. * @param linksklick * Falls auf Linksklicks reagiert werden soll <code>true</code>, sonst <code>false</code> * * @see KlickReagierbar * @see RechtsKlickReagierbar */ public void klickReagierbarAnmelden (Object client, boolean linksklick) { if (maus == null) { // Erstmal Maus erstellen maus = new Maus(1); FensterE.getFenster().mausAnmelden(maus); maus.klickReagierbarAnmelden(this); } Class<?> klasse = client.getClass(); Method[] methoden = klasse.getMethods(); for (int i = 0; i < methoden.length; i++) { if (methoden[i].getName().equals("klickReagieren")) { aufgabenKlick.add(new KlickAuftrag(client, methoden[i], linksklick)); return; } } } /** * Reagiert auf einen Linksklick * * @param punkt beschreibt den Punkt des Linksklicks */ @Override public void klickReagieren (Punkt punkt) { klickSub(punkt.x(), punkt.y(), true); } /** * Sublogik fuer Mausklicks. * * @param x * X-Koordinate des Klicks * @param y * Y-Koordinate des Klicks * @param links * Ob der Klick ein Linksklick war oder nicht */ private void klickSub (int x, int y, boolean links) { for (KlickAuftrag a : aufgabenKlick) { if (a.linksklick == links) { a.ausfuehren(x, y); } } } /** * Reagiert auf einen Rechtsklick * * @param punkt beschreibt den Punkt des Rechtsklicks */ @Override public void rechtsKlickReagieren (Punkt punkt) { klickSub(punkt.x(), punkt.y(), false); } /** * Methode zum Weiterleiten von Tastendrucks an die angemeldeten * * @param code * Der Tastaturcode des Tastendrucks * * @see ea.TastenReagierbar */ @Override public void reagieren (int code) { for (TastenAuftrag t : aufgaben) { t.ausfuehren(code); } } /** * In der TICK-Methode wird die Weitergabe des TICK-Befehls geregelt. */ @Override public void tick () { try { for (Auftrag a : aufgabenT) { if (runde % a.intervall() == 0) { a.ausfuehren(); } } runde++; } catch (ConcurrentModificationException e) { // TODO: Add Handling? } } /** * Ein Auftrag regelt je einen Fake-Ticker. */ private final class Auftrag { /** * Das Intervall */ private final int intervall; /** * Der Client, an dem der Tick aufgerufen wird */ private final Object client; /** * Die aufzurufende TICK-MEthode */ private final Method methode; public Auftrag (Object client, Method tick, int intervall) { this.intervall = intervall; this.client = client; methode = tick; } /** * Führt einen Tick aus. */ public final void ausfuehren () { try { methode.invoke(client, new Object[0]); } catch (InvocationTargetException | IllegalAccessException e) { e.printStackTrace(); } } /** * @return Das Intervall des gelagerten Objektes */ public int intervall () { return intervall; } /** * @return Das Objekt, das als "Client"-Ticker immer wieder aufgerufen wird. */ public Object client () { return client; } } /** * Ein TastenAuftrag regelt den Aufruf eines TastenReaktions-Interface. */ private final class TastenAuftrag { /** * Die aufzurufende Methode */ private final Method methode; /** * Das Objekt, an dem diese Methode ausgefuehrt werden soll! */ private final Object client; /** * Erstellt einen Tastenauftrag * * @param client * Das Objekt, an dem der Job ausgefuehrt werden soll. * @param m * Die auszufuehrende Methode. */ public TastenAuftrag (Object client, Method m) { this.client = client; methode = m; } /** * Führt die Methode einmalig aus. * * @param code * Der Tastaturcode, der mitgegeben wird. */ public void ausfuehren (int code) { try { methode.invoke(client, code); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (java.lang.IllegalAccessException e) { e.printStackTrace(); } } } /** * Auftrag für einen Klick-Listener */ private final class KlickAuftrag { private final Method methode; private final Object client; private final boolean linksklick; private KlickAuftrag (Object c, Method m, boolean linksklick) { methode = m; client = c; this.linksklick = linksklick; } /** * Führt die Methode am Client aus. * * @param x * Die zu uebergebene X-Koordinate des Klicks. * @param y * Die zu uebergebene Y-Koordinate des Klicks. */ private void ausfuehren (int x, int y) { try { methode.invoke(client, new Object[] {x, y}); } catch (InvocationTargetException e) { e.printStackTrace(); } catch (java.lang.IllegalAccessException e) { e.printStackTrace(); } } } }