Home > Informatik > Stufe EF > 9. zweidimensionale Arrays

9.2 Eine Graphikanwendung

Notenliste - Graphikanwendung - zelluläre Automaten

Dieser Workshop dient allein der Vertiefung der Graphikprogrammierung sowie des Umgangs mit zweidimensionalen Arrays. Übungen sind für diesen fakultativen Workshop nicht vorgesehen. Falls Sie diesen Workshop im Informatikunterricht an Ihrer Schule bearbeiten, wird sich Ihre Lehrerin oder Ihr Lehrer sicherlich eigene Übungen ausgedacht haben. Oder auch nicht.

Betrachten Sie folgenden Screenshot:

Beschreibung siehe folgenden Text

Die Graphikanwendung mit vier Buttons
Autor: Ulrich Helmich 2026, Lizenz: Public Domain

Die Graphikanwendung, die Sie in diesem Workshop programmieren soll, ist nicht besonders kompliziert, könnte aber das Grundgerüst für ein eigenes kleines Spiel werden.

Sie sehen eine Spielfläche, die aus 10 x 10 Spielfeldern besteht. Ein roter Spielstein kann mithilfe der vier Buttons gesteuert werden. Das war's eigentlich schon.

Die Spielfläche besteht aus 10 x 10 Spielfeldern. Wir werden in dem Workshop so vorgehen, dass wir zuerst mal eine Klasse Feld programmieren (Schritt 1) und dann einen zweidimensionalen Array dieser Spielfelder erzeugen (Schritt 2).

Im Schritt 3 sorgen wir dafür, dass die Felder verschiedene Zustände einnehmen können: Leer / mit rotem Spielstein belegt. Schritt 4 ermöglicht uns, die Felder mit variabler Breite zu initialisieren.

Im Schritt 5 programmieren wir die vier Buttons, anschließend statten wir die App in Schritt 6 mit einer Ereignisverwaltung aus, so dass man den roten Kreis mithilfe der Buttons steuern kann.

In den folgenden Schritten erweitern wir die Anwendung noch etwas, bis wir am Ende ein kleines Krümelmonster haben, das Brotkrümel frisst und dabei wächst.

Aber eines nach dem anderen…

Schritt 1 - Klasse Feld↑

Wir verfahren nach dem bewährten Prinzip der Bottom-Up-Programmierung und entwickeln zunächst eine Klasse für ein einzelnes Feld (eines der kleinen Quadrate in unserer späteren Grafikanwendung).

Ein Feld soll quadratisch dargestellt werden. Das Attribut status speichert, ob es sich um ein leeres oder ein von der Spielfigur besetztes Feld handelt. Außerdem müssen wir die Position des Feldes innerhalb des späteren Spielfeldrasters speichern. Dazu dienen die Attribute x und y. Hier ist der Quelltext der Klasse Feld, Version 1:

import java.awt.Color;
import java.awt.Graphics;

public class Feld
{
    private int status;
    private int x, y;

    public Feld(int x, int y)
    {
        status = 0;
        this.x = x;
        this.y = y;
    }

    public void anzeigen(Graphics g)
    {
        if (status == 0)
        {
            // Gelben Hintergrund zeichnen
            g.setColor(new Color(255, 255, 191));
            g.fillRect(50 + x * 40, 50 + y * 40, 40, 40);

            // Schwarzen Rahmen zeichnen
            g.setColor(Color.BLACK);
            g.drawRect(50 + x * 40, 50 + y * 40, 40, 40);
        }
    }
}

Da können wir noch nichts Besonderes entdecken, nicht wahr? Die wichtigen Informationen werden in einfachen privaten Instanzvariablen gespeichert. Der Konstruktor übernimmt die übergebenen Koordinaten und setzt den Status des Feldes auf 0 (leer). Die Methode anzeigen() zeichnet das Feld in der noch zu entwickelnden Grafikanwendung. Dazu verwendet sie die übergebenen Zeichenfunktionen des Graphics-Objekts.

Schritt 2 - Ein Array von Feldern↑

Jetzt schreiben wir eine kleine Test-Anwendung, mit der wir die Klasse Feld ausprobieren können. Dazu erzeugen wir nicht nur ein einzelnes Feld, sondern gleich 100 Felder, die in einem zweidimensionalen Array gespeichert werden - womit wir beim eigentlichen Thema wären.

Hier ist der Quelltext der Graphik-Anwendung:

import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Graphikanwendung extends JFrame
{
    private Feld[][] testfeld;

    public Graphikanwendung()
    {
        initFelder();

        setTitle("Eine kleine Graphikanwendung");
        setSize(500, 500);
        setResizable(false);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Zeichenfläche einrichten
        JPanel zeichenflaeche = new JPanel()
        {
            @Override
            protected void paintComponent(Graphics g)
            {
                super.paintComponent(g);

                for (int x = 0; x < 10; x++)
                    for (int y = 0; y < 10; y++)
                        testfeld[x][y].anzeigen(g);
            }
        };

        add(zeichenflaeche);
        setVisible(true);
    }

    public void initFelder()
    {
        testfeld = new Feld[10][10];

        for (int x = 0; x < 10; x++)
            for (int y = 0; y < 10; y++)
                testfeld[x][y] = new Feld(x, y);
    }

    public static void main(String[] args)
    {
        javax.swing.SwingUtilities.invokeLater(() -> new Graphikanwendung());
    }
}

Diese Anwendung enthält nicht nur ein einzelnes Objekt der Klasse Feld, sondern einen zweidimensionalen Array mit insgesamt 100 Feldern. Die Methode initFelder() erzeugt die einzelnen Objekte und speichert sie im Array.

Für die grafische Darstellung verwenden wir ein JPanel. Das ist eine Zeichenfläche, auf der wir unsere Felder darstellen können. Die Methode paintComponent() wird von Java automatisch aufgerufen, wenn die Zeichenfläche neu gezeichnet werden muss. Dabei wird für jedes Feld die bereits bekannte Methode anzeigen() aufgerufen.

⇒ Testen Sie den neuen Quelltext. Kompilieren Sie das Projekt. Klicken Sie mit der rechten Maustaste auf den orangefarbenen Kasten der Klasse Graphikanwendung und wählen Sie im Kontextmenü die Methode main(String[] args) aus.

Wenn Sie alles richtig gemacht haben, müssten Sie folgendes Bild sehen:

Ein Spielfeld aus 10 mal 10 gelben Quadraten

Die erste Version der kleinen Graphikanwendung

Schritt 3 - Feld mit Zuständen↑

Wir ergänzen die anzeigen()-Methode der Klasse Feld so, dass zwei verschiedene Zustände angezeigt werden können. Den jeweiligen Zustand speichern wir in der Instanzvariablen status:

public void anzeigen(Graphics g)
{
    // Gelben Hintergrund zeichnen
    g.setColor(new Color(255, 255, 191));
    g.fillRect(50 + x * 40, 50 + y * 40, 40, 40);

    // Schwarzen Rahmen zeichnen
    g.setColor(Color.BLACK);
    g.drawRect(50 + x * 40, 50 + y * 40, 40, 40);

    // Bei Status 1 einen roten Kreis zeichnen
    if (status == 1)
    {
        g.setColor(Color.RED);
        g.fillOval(50 + x * 40, 50 + y * 40, 40, 40);
    }
}

Zunächst wird nur ein leeres gelbes Feld gezeichnet. Hat das Attribut status aber den Wert 1, wird auf das gelbe Feld ein roter Kreis gezeichnet.

Übung für Interessierte

Der einfache rote Kreis sieht nicht allzu plastisch aus. Vielleicht können Sie ihn ja schöner gestalten, mit Schatten, Reflexen, Lichtpunkten oder etwas Ähnlichem.

Das Attribut status muss jetzt natürlich auch verändert werden können, sonst macht die Sache ja keinen Sinn. Dazu ergänzen wir die Klasse Feld um eine neue Methode:

    public void setzeStatus(int neuerStatus)
    {
       status = neuerStatus;
    }

In der Klasse Graphikanwendung ergänzen wie die initFelder()-Methode um eine Zeile:

    public void initFelder()
    {
        testfeld = new Feld[10][10];
        for (int x=0; x < 10; x++)
            for (int y=0; y < 10; y++)
                testfeld[x][y] = new Feld(x,y);
                
        testfeld[5][5].setzeStatus(1);
    }

Auf diese Weise soll dann das Feld mit den Koordinaten (5,5) einen roten Kreis enthalten. Wenn wir das Ganze kompilieren und zum Laufen bringen, können wir uns davon überzeugen, dass der bisherige Quelltext funktioniert:

Die zweite Version der kleinen Graphikanwendung

Schritt 4 - Flexible Felder↑

Wir machen zunächst die Klasse Feld flexibler. Die Breite der Felder war bisher auf 40 Pixel festgelegt. Jetzt sollen die Benutzer der Klasse selbst entscheiden, wie breit die Felder sein sollen.

Um dies zu erreichen, müssen wir zwei Dinge erledigen. Erstens muss die Breite des Feldes innerhalb der Klasse Feld gespeichert werden. Dies sollte kein Problem sein: Ein neues Attribut breite, und das Problem ist gelöst. Beim Erzeugen eines Feld-Objektes muss dann die gewünschte Breite als Parameter übergeben werden. Betrachten wir den neuen Konstruktor:

    public Feld(int x, int y, int b)
    {
        status = 0;
        this.x = x;
        this.y = y;
        breite = b;
    }

Wie sich diese flexiblere Lösung auf das Zeichnen der Felder auswirkt, zeigt der Quelltext der anzeigen()-Methode:

 public void anzeigen(Graphics g)
    {
        // Gelben Hintergrund zeichnen
        g.setColor(new Color(255, 255, 191));
        g.fillRect(50 + x * breite, 50 + y * breite, breite, breite);

        // Schwarzen Rahmen zeichnen
        g.setColor(Color.BLACK);
        g.drawRect(50 + x * breite, 50 + y * breite, breite, breite);

        // Bei Status 1 einen roten Kreis zeichnen
        if (status == 1)
        {
            g.setColor(Color.RED);
            g.fillOval(50 + x * breite, 50 + y * breite, breite, breite);
        }
    }

Was hier noch nicht flexibel gestaltet wurde, ist die Position des Feldes bzw. der Gesamtheit der Felder. Die links liegenden Felder beginnen immer noch bei x=50 und y=50.

Übung für Interessierte

Machen Sie auch die Position der Felder etwas flexibler. Wenn die Felder beispielsweise nur 20 Pixel breit sind, wäre es schön, wenn das Gesamtfeld in der Mitte des Anwendungsfensters liegen würde und nicht links oben wie in der nächsten Abbildung.

Das Gesamtfeld liegt nicht optimal
Ersteller: Ulrich Helmich 2026

Schritt 5 - Anwendung mit Buttons↑

Wir wollen jetzt die Anwendung mit vier Buttons ausstatten, die wir up, down, left und right nennen. Hier der lauffähige Quelltext der erweiterten Graphikanwendung:

import java.awt.*;
import javax.swing.*;

import java.awt.Graphics;
import javax.swing.*;

public class Graphikanwendung extends JFrame
{
    private Feld[][] testfeld;
    private JButton up, down, left, right;

    public Graphikanwendung()
    {
        initFelder();
        initButtons();

        setTitle("Eine kleine Graphikanwendung");
        setSize(500, 600);
        setResizable(false);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // Zeichenfläche einrichten
        JPanel zeichenflaeche = new JPanel()
            {
                @Override
                protected void paintComponent(Graphics g)
                {
                    super.paintComponent(g);

                    for (int x = 0; x < 10; x++)
                        for (int y = 0; y < 10; y++)
                            testfeld[x][y].anzeigen(g);
                }
            };

        setLayout(null);

        zeichenflaeche.setBounds(0, 0, 500, 460);
        add(zeichenflaeche);

        setVisible(true);
    }

    public void initFelder()
    {
        testfeld = new Feld[10][10];

        for (int x = 0; x < 10; x++)
            for (int y = 0; y < 10; y++)
                testfeld[x][y] = new Feld(x, y, 40);

        testfeld[5][5].setzeStatus(1);
    }

    public void initButtons()
    {
        up    = new JButton("Hoch");
        down  = new JButton("Runter");
        left  = new JButton("Links");
        right = new JButton("Rechts");

        up.setBounds(200, 460, 100, 30);
        down.setBounds(200, 520, 100, 30);
        left.setBounds(160, 490, 100, 30);
        right.setBounds(240, 490, 100, 30);

        add(up);
        add(down);
        add(left);
        add(right);
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(() -> new Graphikanwendung());
    }
}

Neu an unserer Graphikanwendung sind die vier Schaltflächen (englisch: Buttons), mit denen wir später die rote Spielfigur auf dem Spielfeld bewegen wollen. Solche Schaltflächen kennt man aus vielen Programmen: Wenn man sie mit der Maus anklickt, wird eine bestimmte Aktion ausgeführt.

Beschreibung siehe folgenden Text

Die Graphikanwendung mit vier Buttons
Autor: Ulrich Helmich 2026, Lizenz: Public Domain

In Java verwenden wir für solche Schaltflächen Objekte der Klasse JButton. Diese Klasse gehört zur sogenannten Swing-Bibliothek, mit der man grafische Benutzeroberflächen erstellen kann.

Zunächst deklarieren wir vier Attribute:

private JButton up, down, left, right;

Die vier Instanzvariablen sollen später die entsprechenden Button-Objekte speichern. Die eigentlichen Objekte werden erst in der Methode initButtons() erzeugt:

up    = new JButton("Hoch");
down  = new JButton("Runter");
left  = new JButton("Links");
right = new JButton("Rechts");

Mit dem Schlüsselwort new erzeugen wir vier neue Objekte der Klasse JButton. Die Texte in den Klammern legen fest, welche Beschriftung auf den Schaltflächen erscheint.

Als Nächstes legen wir fest, wo die Buttons erscheinen sollen und wie groß sie sind. Dazu verwenden wir die Methode setBounds(); hier am Beispiel des up-Buttons erläutert:

up.setBounds(200, 460, 100, 30);

Die vier Zahlen haben folgende Bedeutung:

  • 200: Abstand vom linken Rand in Pixeln
  • 460: Abstand vom oberen Rand in Pixeln
  • 100: Breite des Buttons in Pixeln
  • 30: Höhe des Buttons in Pixeln

Die anderen drei Buttons werden auf die gleiche Weise angeordnet.

Die vier Objekte der Klasse Button existieren jetzt bereits, sind aber in dem Fenster der Anwendung noch nicht zu sehen. Jede Anwendung enthält eine interne Liste von Komponenten (Buttons, Textfelder, Listboxen etc.), und wir müssen die vier Buttons jetzt in diese Liste aufnehmen. Dies geschieht mit der Methode add():

add(up);
add(down);
add(left);
add(right);

Die Anweisung setLayout(null) im Konstruktor sorgt dafür, dass wir die Positionen und Größen der Buttons selbst festlegen können. Ohne diese Anweisung würde Java die Anordnung der Komponenten selbst übernehmen.

Durch die Anwendung der add()-Befehle sind die vier Buttons jetzt zwar in dem Graphikfenster sichtbar, doch wenn man einen dieser Buttons anklickt, passiert noch nichts.

Damit ein Klick auf einen Button eine bestimmte Aktion auslöst, müssen wir eine sogenannte Ereignisbehandlung programmieren. Darum kümmern wir uns im nächsten Schritt.

Schritt 6 - Buttons mit Funktionen↑

Wir wollen die Buttons nun mit Funktionalität ausstatten, zunächst aber nur den up-Button, um mal ganz einfach anzufangen. Wenn auf den up-Button geklickt wird, soll das Feld über dem markierten Feld ebenfalls markiert werden. Wir beginnen diesen Schritt 6 also zunächst mit einer ganz einfachen Aufgabe, die es aber in sich hat.

Zunächst einmal die gute Nachricht: An der Klasse Feld müssen wir diesmal nichts ändern. Was für ein Glück!

Die zweite gute Nachricht: Wir müssen die Methode initButtons() der Graphikanwendung nur um eine Anweisung ergänzen.

Jetzt die schlechte Nachricht: Diese Anweisung enthält einen sogenannten Lambda-Ausdruck, der auf den ersten Blick recht kompliziert aussieht:

up.addActionListener(e ->
{
	 testfeld[5][4].setzeStatus(1);
	 repaint();
});

Erklärung des Lambda-Ausdrucks

Schauen wir uns den neuen Quelltext Schritt für Schritt an.

1. up.addActionListener(...)

Mit dieser Anweisung teilen wir dem Button up mit, dass er auf bestimmte Aktionen des Benutzers reagieren soll. Das Anklicken des Buttons mit der Maus ist eine solche Aktion.

Der Begriff ActionListener bedeutet sinngemäß "Aktionsüberwacher". Ein ActionListener reagiert darauf, dass eine bestimmte Aktion stattfindet. Sobald der Benutzer auf den Button klickt, werden die Anweisungen ausgeführt, die wir dafür vorgesehen haben.

2. e -> - der Lambda-Ausdruck

Der Pfeil -> ist ein wichtiger Bestandteil des Lambda-Ausdrucks. Mit einem Lambda-Ausdruck können wir auf einfache Weise festlegen, welche Anweisungen bei einer bestimmten Aktion ausgeführt werden sollen.

Das e vor dem Pfeil ist eine Variable, die Informationen über die ausgelöste Aktion enthält. In unserem Beispiel benötigen wir diese Informationen allerdings nicht. Trotzdem muss der Parameter angegeben werden, weil der ActionListener ihn erwartet.

Hinter dem Pfeil stehen die Anweisungen, die beim Anklicken des Buttons ausgeführt werden sollen. Wie auch bei while-Schleifen oder if-Anweisungen üblich, können mehrere Anweisungen durch geschweifte Klammern zu einem Block zusammengefasst werden.

Die erste Anweisung (testfeld[5][4].setzeStatus(1);) setzt den Status des Feldes (5,4) auf den Wert 1, so dass dort ein roter Kreis dargestellt werden kann.

Die zweite Anweisung (repaint();) fordert eine Neuzeichnung der grafischen Oberfläche an. Ohne diese Anweisung würde zwar der Status des Feldes auf 1 gesetzt, aber der neue rote Kreis wäre zunächst nicht sichtbar.

Zusammenfassung

Was passiert beim Anklicken des Buttons?

  1. Der Benutzer klickt auf den up-Button.
  2. Der ActionListener erkennt diese Aktion.
  3. Der Lambda-Ausdruck legt fest, welche Anweisungen in diesem Fall ausgeführt werden sollen.
  4. Das Feld (5,4) erhält den Status 1.
  5. Die Grafik wird neu gezeichnet, sodass man den zweiten roten Kreis ebenfalls sieht.

Merke:

Lambda-Ausdruck

Mit addActionListener() können wir einem Button eine Funktion zuweisen. Der Lambda-Ausdruck mit dem Pfeil -> legt fest, was passieren soll, wenn der Button angeklickt wird.

Schritt 7 - Der Kreis bewegt sich

Wir wollen die Anwendung nun so verändern, dass sich der rote Kreis tatsächlich nach oben bewegt, wenn wir auf den up-Button klicken. Dazu muss das bisherige Feld wieder den Status 0 erhalten, während das darüberliegende Feld auf Status 1 gesetzt wird.

Bei jedem weiteren Klick auf den up-Button soll sich der Kreis um ein Feld nach oben bewegen, bis er schließlich den oberen Rand erreicht hat.

Was müssen wir dazu am Quelltext der Graphikanwendung verändern?

Die Graphikanwendung muss die aktuelle Position des roten Kreises kennen. Dazu ergänzen wir die Klasse Graphikanwendung um zwei Instanzvariablen:

private int posX = 5;

private int posY = 5;

Diese beiden Variablen speichern die aktuelle Position des roten Kreises. Zu Beginn befindet sich der Kreis an der Position (5,5).

public class Graphikanwendung extends JFrame
{
	 private Feld[][] testfeld;
	 private JButton up, down, left, right;

	 // Aktuelle Position des roten Kreises
	 private int posX = 5;
	 private int posY = 5;

Die beiden Instanzvariablen deklarieren und initialisieren wir im Rumpf der Klasse, außerhalb der Methoden, genau wie die anderen Instanzvariablen auch.

Die Methode paintComponent() muss zum Glück nicht geändert werden. In der Methode initFelder() greifen wir jetzt auf die beiden neuen Instanzvariablen zurück:

public void initFelder()
{
	 testfeld = new Feld[10][10];

	 for (int x = 0; x < 10; x++)
		  for (int y = 0; y < 10; y++)
				testfeld[x][y] = new Feld(x, y, 40);

	 // Startposition des roten Kreises
	 testfeld[posX][posY].setzeStatus(1);
}

In der Methode initButtons() müssen wir nun den Anweisungsblock des Lambda-Ausdrucks erweitern:

up.addActionListener(e ->
{
	 if (posY > 0)
	 {
		  // Bisheriges Feld zurücksetzen
		  testfeld[posX][posY].setzeStatus(0);

		  // Eine Position nach oben
		  posY--;

		  // Neues Feld markieren
		  testfeld[posX][posY].setzeStatus(1);

		  // Grafik aktualisieren
		  repaint();
	 }
});

Ich habe diesen Quelltext ausführlich kommentiert, weil Lambda-Ausdrücke für die Stufe EF laut Lehrplan nicht vorgesehen sind. Aber eigentlich ist die Sache gar nicht so schwer, wenn man sie erst einmal verstanden hat.

Schauen wir uns zunächst die Bedingung if (posY > 0) an. Die oberste Zeile unseres zweidimensionalen Arrays hat den Index 0. Befindet sich der rote Kreis bereits in dieser Zeile, darf er nicht weiter nach oben bewegt werden. Deshalb werden die folgenden Anweisungen nur ausgeführt, wenn posY größer als 0 ist.

Ist diese Bedingung erfüllt, finden vier Aktionen statt:

  1. Das Feld, auf dem sich der rote Kreis bisher befand, wird wieder auf den Status 0 gesetzt.
  2. Die Instanzvariable posY wird um 1 dekrementiert, damit der Kreis nach oben wandert.
  3. Das darüberliegende Feld wird auf Status 1 gesetzt.
  4. Schließlich wird eine Neuzeichnung der Grafik angefordert, sodass der rote Kreis an seiner neuen Position erscheint.

Übung 9.2-1

Erweitern Sie die Anwendung so, dass auch die drei anderen Buttons korrekt funktionieren! Achten Sie darauf, dass der rote Kreis das Spielfeld nicht verlassen darf.

Lösungsvorschlag auf den Seiten für Lehrer(innen) / Nähere Infos dazu

Schritt 8 - Ein kleines Spiel

Wir wollen nun ein kleines Spiel implementieren, das folgendermaßen abläuft:

Einige Felder des Spielbretts enthalten Brotkrümel, die durch kleine organgefarbene Quadrate dargestellt werden. Hier kann gut ein Zufallsgenerator eingesetzt werden, der vielleicht 10 Felder des Spielfeldes mit Brotkrümeln versieht.

Der Kreis hat am Anfang einen sehr kleinen Durchmesser von vielleicht 8 Pixeln.

Jedes Mal, wenn der Kreis nun auf ein Feld mit einem Brotkrümel gelangt, wird der Krümel gefressen. Der Durchmesser des Kreises wächst anschließend um zwei Pixel.

Auf diese Weise haben wir ein kleines Krümelmonster programmiert.

Das Spielfeld mit den Brotkrümeln
Autor: Ulrich Helmich 2026, Lizenz: Public Domain

Dieses Bild zeigt das Krümelmonster, nachdem es bereits einen Brotkrümel gefressenhat.

Übung 9.2-2

Erweitern Sie die Anwendung entsprechend.

Individuelle Lösungen, eventuell mit KI-Hilfe!