# Java MVC Pattern richtig anwenden



## NicoBec (28. Feb 2018)

Hey,
ich befasse mich gerade mit der korrekten Anwendung des MVC Patterns für das Spiel Breakout. 

Ein paar Fragen dazu bleiben bei mir aber ungelöst. 
Bricks, Ball und Panel werden im View angezeigt und ausgegeben und die Koordinaten dafür berechne ich im Model. Wo werden aber die Objekte generell erstellt?
Und was geschieht in der Console? Mehr Userinput als die Bewegung des Panels habe ich ja nicht. 
Und dann bleibt bei mir noch die Frage, wie och alles so "offen" wie möglich verknüpfen kann, sodass z.B. die View auf die Daten Model zugreifen kann.

Bin für ein bisschen Aufklärung sehr dankbar!
LG


----------



## MoxxiManagarm (28. Feb 2018)

Die  View  erhält eine Referenz auf das Model. Aber die View sollte nur lesend auf das Model zugreifen.
Model und View werden vom Controller erstellt.
Die Benutzereingabe sollte ebenfalls vom Controller verwaltet werden. Der Kontroller kann z.B. KeyListener implementieren. Viele Implementieren die Usereingabe aber auch in der View und senden mit einem KeyEvent einen Funktionsaufruf an den Controller. Das ist ebenfalls ok. Letztendlich unterscheidet sich das nur in der Darstellung, ob der Benutzer mit dem Controller oder der View interargiert. Die Interaktion mit dem Controller entspricht eher dm MVC-Konzept, jedoch ist die Interaktion mit der View flexibler in Bezug auf verschiedene Plattformen. So ist z.B. die Bewegung nach Links anders auf einem Smartphone ausgelöst als auf dem Desktop-PC. Aus Controllersicht ist jedoch beides eine Bewegung nach Links.


```
BrickModel model = new BrickModel();
BrickView view = new BrickView(model);
```


----------



## NicoBec (28. Feb 2018)

@MoxxiManagarm Hey, ich danke dir für die schnelle Antwort!
Mir ist leider nicht 100% klar, was genau in welches Pattern kommt. 
Ich würde jetzt folgendermaßen vorgehen:
den MouseListener in der Control implementieren, damit er die Signale an die Model weitergibt. Hier würde ich Berechnungen für die Ballgeschwindigkeit, die Collisions und weitere Spielphysik einbauen. 
In die View würde ich letztlich nur die Objekte via GraphicsProgram einfügen.
Aber wo kommen z.B. die Methoden rein, die einen Brick bei einer Collision verschwinden lassen und wie bzw. in welchem Fenster (M,V oder C) wird das gesamte Game gestartet?


----------



## MoxxiManagarm (28. Feb 2018)

Der MVC-Part wird im Controller gestartet, also der Controller existiert zuerst. Der Controller erstellt Model und View in der Reihenfolge. Der Controller kann z.B. von der main gestarted werden oder eben durch irgendwas anderes.

Das Model sollte keine große Logik erhalten. Im einfachsten Fall hat das nur getter und setters. Die Kollision sollte m.E. im Controller passieren und der Controller sagt dem Model, dass der Brick entfernt werden muss. In der Regel sind View und Model recht übersichtlich, wo hingegen der Controller den größten Umfang besitzt.


----------



## mrBrown (28. Feb 2018)

NicoBec hat gesagt.:


> den MouseListener in der Control implementieren, damit er die Signale an die Model weitergibt. Hier würde ich Berechnungen für die Ballgeschwindigkeit, die Collisions und weitere Spielphysik einbauen.


Die Berechnungen gehören in das Model - da gehört alles Darstellungs-Unabhängige zu. Das Model hat eine Schnittstelle für Aktionen, die der Nutzer tätigen kann - und nur diese übermittelt der Controller.



NicoBec hat gesagt.:


> In die View würde ich letztlich nur die Objekte via GraphicsProgram einfügen.


Richtig.



NicoBec hat gesagt.:


> Aber wo kommen z.B. die Methoden rein, die einen Brick bei einer Collision verschwinden lassen und wie bzw. in welchem Fenster (M,V oder C) wird das gesamte Game gestartet?


Das der Brick bei einer Kollision verschwindet, gehört doch zur "Domäne" des Spiels und nicht zur Umsetzung für deinen Bildschirm -> also Model 
Die Initialisierung läuft einfach in einer davon getrennten Methode/Klasse, zB einfach in der Main.
Was meinst du mit "in welchem Fenster"? Das Fenster ist ja eben die View.




MoxxiManagarm hat gesagt.:


> Model und View werden vom Controller erstellt.
> [...]
> Die Interaktion mit dem Controller entspricht eher dm MVC-Konzept, jedoch ist die Interaktion mit der View flexibler in Bezug auf verschiedene Plattformen. So ist z.B. die Bewegung nach Links anders auf einem Smartphone ausgelöst als auf dem Desktop-PC. Aus Controllersicht ist jedoch beides eine Bewegung nach Links.


Im eigentlichen Pattern werden UI und Model getrennt initialisiert, und in ersterer initialisiert die View den Controller.
Es geht auch nicht darum, den Controller wieder zu verwenden, der darf auch ruhig von der View abhängig sein. Für ein anderes Frontend würde man beides gemeinsam ersetzen


----------



## NicoBec (28. Feb 2018)

@MoxxiManagarm Alles klar, ich danke dir. Stört es dich, wenn ich dir meine Codeidee per PN zukommen lasse?


----------



## mrBrown (28. Feb 2018)

MoxxiManagarm hat gesagt.:


> Das Model sollte keine große Logik erhalten.


Das ist das häufigste Problem bei MVC-Umsetzungen :/


----------



## MoxxiManagarm (28. Feb 2018)

Ich hab mich wegen gerade wegen mrBrown nochmal belesen und merke ich habe was falsches gelehrt bekommen... Hör mal lieber auf ihn mein Anschauungsbild stellt sich gerade auf den Kopf x) Aber ich bin nachwievor der Meinung Controller war zuerst und Controller erstellt View und Model.


----------



## NicoBec (28. Feb 2018)

@mrBrown  Kann ich dann dir mal meinen Lösungsansatz schicken?


----------



## mrBrown (28. Feb 2018)

kannst du machen, du kannst ihn auch einfach hier posten, wenn es dir nichts ausmacht


----------



## NicoBec (28. Feb 2018)

Klar, wollte damit nur nicht jeden nerven. Er ist Im Moment noch durcheinander, weil ich gerade anfange das Pattern umzubauen (Ich hatte Breakout bereit fertig und es hat top funktioniert und dann wurde mir gesagt, dass das Pattern das Falsche ist:/ )


----------



## NicoBec (28. Feb 2018)

Einmal die Model:

```
package brickBracker;

import java.awt.Color;
import java.awt.Font;
import java.awt.event.MouseEvent;
import java.util.Observable;

import acm.graphics.GLabel;
import acm.graphics.GObject;
import acm.graphics.GOval;
import acm.graphics.GRect;
import acm.program.ConsoleProgram;
import acm.util.RandomGenerator;

public class Model extends Observable {

    protected static final int WIDTH = 1600;
    protected static final int PADDLE_WIDTH = 60;
    protected static final int PADDLE_HEIGHT = 10;
    protected static final int PADDLE_Y_OFFSET = 30;
    protected static final int NBRICK_ROWS = 10;
    protected static final int NBRICKS_PER_ROW = 10;
    protected static final int BRICK_HEIGHT = 8;
    protected static final int BRICK_SEP = 4;
    protected static final int BRICK_WIDTH = (WIDTH - (NBRICKS_PER_ROW - 1) * BRICK_SEP) / NBRICKS_PER_ROW;
    protected static final int BALL_RADIUS = 10;
    protected static final int DELAY = 10;

    protected static final int BRICK_Y_OFFSET = 70;
    protected double vx;
    protected double vy;
    protected RandomGenerator rgen = RandomGenerator.getInstance();
    protected static int brickCounter = 30;
    int scoreToShow = 0;
    protected GOval ballToG;
    protected GRect panel;
    protected GRect brick;
    protected GLabel score;
    protected GLabel scoreBoard;

    protected static GOval ballToGo;

    private void getBallVelocity() {
        vy = -4.0;
        vx = rgen.nextDouble(1.0, 3.0);
        if (rgen.nextBoolean(0.5)) {
            vx = -vx;
        }
    }

    public static void moveBall() {

        if (1 < 2) {
            try {
                Thread.sleep(10);
                ballToGo.move(vx, vy);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
            }

        }

        GObject collider = getCollidingObject();

        if ((ballToGo.getX() - vx <= 0 && vx < 0)
                || (ballToGo.getX() + vx >= (getWidth() - BALL_RADIUS * 2) && vx > 0)) {
            vx = -vx;
        }
        if ((ballToGo.getY() - vy <= 0 && vy < 0)) {
            vy = -vy;
        }

        if (collider == panel) {

            if (ballToGo.getY() >= ((getHeight() - PADDLE_Y_OFFSET - PADDLE_HEIGHT - BALL_RADIUS * 2) - 8)) {
                vy = -vy;
            }
        }

        else if (collider != null) {
            remove(collider);
            brickCounter--;
            remove(scoreBoard);
            this.scoreToShow++;
            GLabel scoreBoard = new GLabel("SCORE: " + scoreToShow, 10, 750);
            scoreBoard.setColor(Color.WHITE);
            scoreBoard.setFont(Font.MONOSPACED);
            add(scoreBoard);
            vy = -vy;
        }
        pause(DELAY);

    }

    protected GObject getCollidingObject() {

        if ((getElementAt(ballToGo.getX(), ballToGo.getY())) != null) {
            return getElementAt(ballToGo.getX(), ballToGo.getY());
        } else if (getElementAt((ballToGo.getX() + BALL_RADIUS * 2), ballToGo.getY()) != null) {
            return getElementAt(ballToGo.getX() + BALL_RADIUS * 2, ballToGo.getY());
        } else if (getElementAt(ballToGo.getX(), (ballToGo.getY() + BALL_RADIUS * 2)) != null) {
            return getElementAt(ballToGo.getX(), ballToGo.getY() + BALL_RADIUS * 2);
        } else if (getElementAt((ballToGo.getX() + BALL_RADIUS * 2), (ballToGo.getY() + BALL_RADIUS * 2)) != null) {
            return getElementAt(ballToGo.getX() + BALL_RADIUS * 2, ballToGo.getY() + BALL_RADIUS * 2);
        } else {
            return null;
        }
    }

}
```

Die Control:


```
package brickBracker;

import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;

import acm.graphics.GLabel;
import acm.graphics.GObject;

public abstract class Control implements ActionListener {

    private Model model;
    private View view;

    public Control() {
        this.model = new Model();

        this.view = new View();

        this.model.addObserver(this.view);

        // Erzeuge das Fenster
        new Frame(this, view);

    }

    public void run() {

        for (int i = 0; i < 1; i++) {

            Game.setUpGame();
            Game.playGame();
            if (ObjectsToGo.brickCounter == 0) {
                ballToGo.setVisible(false);
                printWinner();
                break;
            }

            if (brickCounter > 0) {
                removeAll();
            }
        }
        if (brickCounter > 0) {
            printGameOver();
        }

    }

    public void actionPerformed(MouseEvent e) {
        panel.setLocation(e.getX(),
                getHeight() - PADDLE_Y_OFFSET - ObjectsToGo.PADDLE_HEIGHT - ObjectsToGo.BALL_RADIUS * 2 + 2);

    }

    public static void setUpGame() {
        drawBricks(getWidth() / 2, BRICK_Y_OFFSET);
        drawPanel();
        drawBall();
    }

    public static void playGame() {
        waitForClick();
        getBallVelocity();
        while (true) {
            moveBall();
            if (ballToGo.getY() >= getHeight()) {
                break;
            }
            if (brickCounter == 0) {
                break;
            }
        }
    }
}
```

und die View:


```
package brickBracker;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.*;
import java.util.Observer;

import acm.graphics.*;
import acm.program.*;
import acm.util.RandomGenerator;

public class View extends GraphicsProgram implements Observer {

    protected static final int WIDTH = 1600;
    protected static final int PADDLE_WIDTH = 60;
    protected static final int PADDLE_HEIGHT = 10;
    protected static final int PADDLE_Y_OFFSET = 30;
    protected static final int NBRICK_ROWS = 10;
    protected static final int NBRICKS_PER_ROW = 10;
    protected static final int BRICK_HEIGHT = 8;
    protected static final int BRICK_SEP = 4;
    protected static final int BRICK_WIDTH = (WIDTH - (NBRICKS_PER_ROW - 1) * BRICK_SEP) / NBRICKS_PER_ROW;
    protected static final int BALL_RADIUS = 10;
    protected static final int DELAY = 10;

    protected static final int BRICK_Y_OFFSET = 70;
    protected double vx;
    protected double vy;
    protected RandomGenerator rgen = RandomGenerator.getInstance();
    protected static int brickCounter = 30;
    int scoreToShow = 0;
    protected GOval ballToG;
    protected GRect panel;
    protected GRect brick;
    protected GLabel score;
    protected GLabel scoreBoard;

    public void drawBall() {
        double x = (getWidth() / 2 - BALL_RADIUS) + 200;
        double y = (getHeight() / 2 - BALL_RADIUS) + 300;
        ballToGo = new GOval(x, y, BALL_RADIUS, BALL_RADIUS);
        ballToGo.setFilled(true);
        ballToGo.setColor(Color.white);
        add(ballToGo);
    }

    protected void drawBricks(double cx, double cy) {

        for (int row = 0; row < NBRICK_ROWS; row++) {

            for (int column = 0; column < NBRICKS_PER_ROW - 7; column++) {

                double x = (cx - (NBRICKS_PER_ROW * BRICK_WIDTH) / 2 - ((NBRICKS_PER_ROW - 1) * BRICK_SEP) / 2
                        + column * BRICK_WIDTH + column * BRICK_SEP) + 755;

                double y = cy + row * BRICK_HEIGHT + row * BRICK_SEP;

                brick = new GRect(x, y, BRICK_WIDTH, BRICK_HEIGHT);
                add(brick);
                brick.setFilled(true);

                if (row < 2) {
                    brick.setColor(Color.RED);
                }
                if (row == 2 || row == 3) {
                    brick.setColor(Color.ORANGE);
                }
                if (row == 4 || row == 5) {
                    brick.setColor(Color.YELLOW);
                }
                if (row == 6 || row == 7) {
                    brick.setColor(Color.GREEN);
                }
                if (row == 8 || row == 9) {
                    brick.setColor(Color.CYAN);

                }

            }

        }
    }

    protected void drawPanel() {

        double x = (getWidth() / 2 - PADDLE_WIDTH / 2) + 190;

        double y = (getHeight() - PADDLE_Y_OFFSET - PADDLE_HEIGHT) + 530;
        panel = new GRect(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
        panel.setFilled(true);
        panel.setFillColor(Color.white);
        panel.setColor(Color.white);
        add(panel);
        addMouseListeners();
    }

    protected void printGameOver() {
        GLabel gameOver = new GLabel("Game Over", getWidth() / 2, getHeight() / 2);
        gameOver.move(-gameOver.getWidth() / 2, -gameOver.getHeight());
        gameOver.setColor(Color.RED);
        add(gameOver);
    }

    protected void printWinner() {
        GLabel Winner = new GLabel("Winner!!", getWidth() / 2, getHeight() / 2);
        Winner.move(-Winner.getWidth() / 2, -Winner.getHeight());
        Winner.setColor(Color.GREEN);
        add(Winner);
    }

}
```


----------



## NicoBec (28. Feb 2018)

Wie man sieht, bin ich im MVC Pattern noch extrem unsicher. Die Schnittstellen bereiten mir bis jetzt noch das größte Problem.


----------



## mrBrown (28. Feb 2018)

Das Problem ist eher, dass das Model zig Grafik-Objekte enthält 

uU kann MVC für so etwas auch ungeeignet sein - eben weil man bei einem solchen Spiel keine klare Trennung von Logik und Darstellung erreichen kann


----------



## NicoBec (28. Feb 2018)

mrBrown hat gesagt.:


> uU kann MVC für so etwas auch ungeeignet sein - eben weil man bei einem solchen Spiel keine klare Trennung von Logik und Darstellung erreichen kann



Dann ist das Problem, dass die MVC Anwendung im Projektauftrag vorgegeben ist.


----------



## mrBrown (28. Feb 2018)

Na dann: nach MVC umstellen 

Erstmal: Das Model enthält nichts, was mit der Darstellung zu tun hat


----------



## NicoBec (28. Feb 2018)

mrBrown hat gesagt.:


> Na dann: nach MVC umstellen



Genau das fällt mir gerade so schwer . Ich kann jetzt alle Objekte von der Model in die View werfen. Aber dann bleiben meiner Meinung nach nur noch die Ball- Panel- und Brickkoordinaten in dem Model. 
Und wenn ich es richtig sehe, kommen Methoden, wie moveBall() in den Controller oder?


----------



## mrBrown (28. Feb 2018)

NicoBec hat gesagt.:


> Aber dann bleiben meiner Meinung nach nur noch die Ball- Panel- und Brickkoordinaten in dem Model.


Ja, aus denen kannst du natürlich sinnvolle Objekte machen 




NicoBec hat gesagt.:


> Und wenn ich es richtig sehe, kommen Methoden, wie moveBall() in den Controller oder?


Naja, der Ball ist Teil des Models, also sollten auch die Methoden zum Bewegen des Models dahin 

Im Controller gibts nur die Funktion, die aus einem Tastendruck das "Ball nach Rechts" macht, und die dazu gehörende Methode im Model aufruft


----------



## NicoBec (28. Feb 2018)

@mrBrown Alles klar, ich danke dir schonmal für die Hilfe!


----------



## MoxxiManagarm (1. Mrz 2018)

Habe noch ein paar Anmerkungen:
1. Das if-Konstrukt bei den Farben für die Rows würde ich zu else-ifs machen. Anderenfalls fragt er unnötiger weise alle anderen if's ab, obwohl er beim ersten bereits fündig wurde.
2. Ich bin kein Fan von diesen Threads und Sleeps! Ich werde immer wieder Timer predigen für diese Anwendungsfälle. acm hat auch einen Timer, welcher von javax.swing.timer erbt.
https://cs.stanford.edu/people/eroberts/jtf/javadoc/complete/index.html?acm/util/SwingTimer.html
Du hättest dann kein while(true) mehr. Alles was dort passiert, passiert dann im ActionEvent. Du hättest kein Thread.sleep() mehr was ohnehin vermutlich nicht ins Model gehört, kein try-catch und kein break. Anstatt break stoppst du deinen Timer dann.
3. 
	
	
	
	





```
for (int i = 0; i < 1; i++)
```
Das wird genau 1x ausgeführt. Wozu dann die Schleife?


----------



## MoxxiManagarm (1. Mrz 2018)

Anmerkung zu meiner Anmerkung mit dem Timer:

https://cs.stanford.edu/people/eroberts/jtf/tutorial/AnimationAndInteractivity.html

Das Tutorials schlägt pause() vor  unter 3-2
https://cs.stanford.edu/people/eroberts/jtf/javadoc/student/acm/program/Program.html#pause(double)

Aber unter 3-10 ist auch die Variante mit dem SwingTimer.

----

Das Tutorials erhebt aber den Eindruck, dass GraphicsProgramm nicht für MVC geeignet ist. Das ist so einer Eier Legende Wollmilchsau. Ist das Vorgabe? Eventuell wäre es sinnvoll auf GCanvas umzusteigen und das in einem Frame zu verpacken.


----------



## NicoBec (1. Mrz 2018)

@MoxxiManagarm Hey, danke nochmal für die Hilfe. ACM ist keine Vorgabe, ich kann also nehmen, was ich möchte. Wie wäre denn deiner Meinung nach der Ansatz in "normalem" Java?


----------



## MoxxiManagarm (1. Mrz 2018)

Nein ich meinte nicht "normales" Java. Du kannst nachwievor bei ACM bleiben. Würde ich auch tun da doch viele Bewegung dabei ist. Aber wenn ich die Doku richtig verstehe, dann hat GraphicsProgram als solches schon alles was man auf die Schnelle brauch. Es hat sogar alle möglichen Listener bereits implementiert. Ich glaube es wird schwer ein vernünftiges MVC daherum zu bauen. GraphicsProgram ist bei ACM aber nicht Pflicht. Du kannst genauso JFrame verwenden. GCanvas erbt von JContainer. Füge deinem Frame einfach das GCanvas hinzu und dem GCanvas kannst du wie gewohnt die anderen Elemente ergänzen.


----------



## MoxxiManagarm (2. Mrz 2018)

Ich habe, auch um mich selbst mit ACM ein wenig auseinanderzusetzen, ein Beispiel gemacht. Es kann nicht viel, es bewegt bisher nur die beiden Paddles, aber es legt denke ich meinen Grundgedanken dar.

Controller:

```
package javaforum.org.pingpong;


import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import acm.util.SwingTimer;

public class PingPongController implements KeyListener {

    private PingPongModel model;
    private PingPongView view;
 
    private SwingTimer ballTimer, paddleTimer1, paddleTimer2;
 
 
    public PingPongController() {
        model = new PingPongModel();
        view = new PingPongView(model);
    
        model.addObserver(view);
    
        view.addKeyListener(this);
    
        ballTimer = new SwingTimer(10, ae -> {
              // TODO: Ballbewegung
        });
    
        paddleTimer1 = new SwingTimer(10, ae -> {
              model.movePaddle(Player.ONE);
        });
    
        paddleTimer2 = new SwingTimer(10, ae -> {
              model.movePaddle(Player.TWO);
        });
      
        view.setVisible(true);
    }
 
    public static void main(String[] args) {
        new PingPongController();
    }

    @Override
    public void keyPressed(KeyEvent arg0) {
        switch (arg0.getKeyCode())  {
        case KeyEvent.VK_W:
            model.setDirection(Player.ONE, Direction.UP);
            if(!paddleTimer1.isRunning()) paddleTimer1.start();
            break;
        case KeyEvent.VK_S:
            model.setDirection(Player.ONE, Direction.DOWN);
            if(!paddleTimer1.isRunning()) paddleTimer1.start();
            break;
        case KeyEvent.VK_UP:
            model.setDirection(Player.TWO, Direction.UP);
            if(!paddleTimer2.isRunning()) paddleTimer2.start();
            break;
        case KeyEvent.VK_DOWN:
            model.setDirection(Player.TWO, Direction.DOWN);
            if(!paddleTimer2.isRunning()) paddleTimer2.start();
            break;
        default:
        }
    }

    @Override
    public void keyReleased(KeyEvent arg0) {
        switch (arg0.getKeyCode())  { // TODO: Verhalten bei up+down
        case KeyEvent.VK_W:
            if(paddleTimer1.isRunning()) paddleTimer1.stop();
            break;
        case KeyEvent.VK_S:
            if(paddleTimer1.isRunning()) paddleTimer1.stop();
            break;
        case KeyEvent.VK_UP:
            if(paddleTimer2.isRunning()) paddleTimer2.stop();
            break;
        case KeyEvent.VK_DOWN:
            if(paddleTimer2.isRunning()) paddleTimer2.stop();
            break;
        default:
        }
    }

    @Override
    public void keyTyped(KeyEvent arg0) {
        switch (arg0.getKeyCode())  {
        case KeyEvent.VK_ENTER:
        case KeyEvent.VK_SPACE:
            if(!ballTimer.isRunning()) { // TODO: nur wenn Spiel im Startzustand
                ballTimer.start();
            }
        default:
        }
    }
}
```

View:

```
package javaforum.org.pingpong;


import java.awt.Dimension;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JFrame;

import acm.graphics.GCanvas;
import acm.graphics.GLine;
import acm.graphics.GObject;
import acm.graphics.GOval;
import acm.graphics.GRect;

@SuppressWarnings("serial")
public class PingPongView extends JFrame implements Observer {
    private HashMap<ObservableObject, GObject> observerObjects;
   
    public PingPongView(PingPongModel model) {
        setTitle("Ping Pong");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       
        GCanvas panel = new GCanvas();
        panel.setPreferredSize(new Dimension(Settings.PANEL_SIZE * 2, Settings.PANEL_SIZE));
        add(panel);
       
        GLine line = new GLine(Settings.PANEL_SIZE, 0, Settings.PANEL_SIZE, Settings.PANEL_SIZE);
        line.setColor(Settings.LINE_COLOR);
        panel.add(line);
       
        observerObjects = new HashMap<ObservableObject, GObject>();
       
        PaddleView paddle1 = new PaddleView(Settings.PADDLE_WIDTH, Settings.PADDLE_HEIGHT, model.getPaddle(Player.ONE).getX(), model.getPaddle(Player.ONE).getY());
        panel.add(paddle1);
        observerObjects.put(model.getPaddle(Player.ONE), paddle1);
       
        PaddleView paddle2 = new PaddleView(Settings.PADDLE_WIDTH, Settings.PADDLE_HEIGHT, model.getPaddle(Player.TWO).getX(), model.getPaddle(Player.TWO).getY());
        panel.add(paddle2);
        observerObjects.put(model.getPaddle(Player.TWO), paddle2);
       
        BallView ball = new BallView(Settings.BALL_RADIUS*2, Settings.BALL_RADIUS*2, model.getBall().getX(), model.getBall().getY());
        panel.add(ball);
        observerObjects.put(model.getBall(), ball);
       
        pack();
    }

    @Override
    public void update(Observable arg0, Object arg1) {
        ObservableObject observableObject = (ObservableObject)arg1;
        GObject observerObject = observerObjects.get(observableObject);
       
        observerObject.setLocation(observableObject.getX(), observableObject.getY());       
    }
   
    private class PaddleView extends GRect {
        public PaddleView(int sizeX, int sizeY, int posX, int posY) {
            super(sizeX, sizeY);
            setLocation(posX, posY);
           
            setFilled(true);
            setFillColor(Settings.PADDLE_COLOR);
            setColor(Settings.PADDLE_COLOR);
        }
       
        @Override
        public void setLocation(double x, double y) {
            super.setLocation(x - Settings.PADDLE_WIDTH/2, y - Settings.PADDLE_HEIGHT/2);
        }
    }
   
    private class BallView extends GOval {
        public BallView(int sizeX, int sizeY, int posX, int posY) {
            super(sizeX, sizeY);
            setLocation(posX, posY);
           
            setFilled(true);
            setFillColor(Settings.BALL_COLOR);
            setColor(Settings.BALL_COLOR);
        }
       
        @Override
        public void setLocation(double x, double y) {
            super.setLocation(x - Settings.BALL_RADIUS, y - Settings.BALL_RADIUS);
        }
    }
}
```

Model:

```
package javaforum.org.pingpong;

import java.util.Observable;

public class PingPongModel extends Observable {
 
    private PaddleModel paddle1, paddle2;
    private BallModel ball;

    public PingPongModel() {
        paddle1 = new PaddleModel(Settings.PADDLE_OFFSET);
        paddle2 = new PaddleModel(Settings.PANEL_SIZE * 2 - Settings.PADDLE_OFFSET);
    
        ball = new BallModel();
    }
 
    public void movePaddle(Player player) {
        setChanged();
        switch(player) {
        case ONE:
            paddle1.move();
            notifyObservers(paddle1);
            break;
        case TWO:
            paddle2.move();
            notifyObservers(paddle2);
            break;    
        }
    }
 
    public void setDirection(Player player, Direction direction) {
        switch(player) {
        case ONE:
            paddle1.setDirection(direction);
            break;
        case TWO:
            paddle2.setDirection(direction);
            break;    
        }
    }
 
    public PaddleModel getPaddle(Player player) {
        switch(player) {
        case ONE:
            return paddle1;
        case TWO:
            return paddle2;    
        }
        return null;
    }
 
    public BallModel getBall() {
        return ball;
    }
}
```


----------



## MoxxiManagarm (2. Mrz 2018)

Zum Verständnis noch:

Paddle

```
package javaforum.org.pingpong;

public class PaddleModel extends ObservableObject{

    private int speed;
    private Direction direction;
  
    public PaddleModel(int x) {
        this.x = x;
        y = Settings.PANEL_SIZE / 2;
      
        speed = Settings.PADDLE_SPEED;
    }  
  
    public int getSpeed() {
        return speed;
    }
  
    public void setDirection(Direction direction) {
        this.direction = direction;
    }
  
    public void move() {
        switch (direction) {
        case UP:
            y -= speed;
            break;
        case DOWN:
            y += speed;
            break;
        }
      
        if (y < (Settings.PADDLE_HEIGHT / 2))
            y = Settings.PADDLE_HEIGHT / 2;
        else if (y > (Settings.PANEL_SIZE - (Settings.PADDLE_HEIGHT / 2)))
            y = Settings.PANEL_SIZE - (Settings.PADDLE_HEIGHT / 2);
    }
}
```

ObservableObject

```
package javaforum.org.pingpong;

public abstract class ObservableObject {
    protected int x, y;
  
    public int getX() {
        return x;
    }
  
    public int getY() {
        return y;
    }
  
    public void setX(int x) {
        this.x = x;
    }
  
    public void setY(int y) {
        this.y = y;
    }
}
```


----------

