communityWir suchen ständig neue Tutorials und Artikel! Habt ihr selbst schonmal einen Artikel verfasst und seid bereit dieses Wissen mit der Community zu teilen? Oder würdet ihr gerne einmal über ein Thema schreiben das euch besonders auf dem Herzen liegt? Dann habt ihr nun die Gelegenheit eure Arbeit zu veröffentlichen und den Ruhm dafür zu ernten. Schreibt uns einfach eine Nachricht mit dem Betreff „Community Articles“ und helft mit das Angebot an guten Artikeln zu vergrößern. Als Autor werdet ihr für den internen Bereich freigeschaltet und könnt dort eurer literarischen Ader freien Lauf lassen.

Battleship - View Drucken E-Mail
Benutzerbewertung: / 88
SchwachPerfekt 
Sonntag, den 10. Mai 2009 um 00:00 Uhr
Beitragsseiten
Battleship
Beginn des Projekts
Model
Netzwerkprogrammierung in Java
Netzwerkprotokolle
Routing
View
Computerspieler
Controller
Alle Seiten

View

Die Präsentationsschicht (engl. View) ist für die Darstellung der benötigten Daten aus dem Model und die Entgegennahme von Benutzerinteraktionen zuständig. Ein Vorteil von MVC ist die strenge Trennung der Präsentationsschicht von der Geschäftlogik mitsamt den Daten. Dieser Vorteil ermöglicht es problemlos eine textbasierten Benutzeroberfläche, als auch eine grafische Benutzeroberfläche, kurz GUI, zu implementieren ohne Änderungen an der Geschäftslogik durchführen zu müssen.

Die View erhält den Zustand, den es anzeigt, direkt vom Model. Wird es beispielsweise vom Model benachrichtigt, dass sich Daten geändert haben, kann die View das Model nach den neuen Daten abfragen um die Oberfläche zu aktualisieren. Die View kann das Model auch dann nach dessen Zustand fragen, wenn es vom Controller aufgefordert wurde, seine Ansicht zu verändern.

Die Präsentationsschicht ist ein Kompositum aus GUI-Komponenten (Labels, Buttons, Textfeldern usw.). Die oberste Komponente enthält andere Komponenten, die wiederum weitere Komponenten enthalten usw., bis man bei den Blattknoten angelangt ist. In einer textbasierten Anwendung, auch Konsolenanwendung genannt, unterscheidet sich die View von einer GUI in einem ganz wesentlichen Punkt. Nicht nur sind keine GUI-Komponenten vorhanden, eine Konsolenanwendung ist im Gegensatz zu einer GUI nicht ereignisgesteuert (event-driven). Dies muss bei der Konzeption zusätzlich berücksichtigt werden.

Spieler und Views

In einem Spiel, wie »Schiffe versenken«, repräsentiert eine View die Ansicht des Spielers auf das Model. Die View reagiert auf Benutzereingaben und leitet die Informationen über den Controller weiter. Der Spieler kann über seine View Einfluss auf die Daten nehmen, sie lesen und auch manipulieren. Kurzum, der Spieler ist für die Interaktion zuständig und entscheidet was zu tun ist.

Es liegt nahe das die View mit einem Spieler gleichsetzbar ist. Dies ist eine wichtige Kernerkenntnis für die Systemarchitektur. Ein Spieler, egal welcher Art, wird durch eine separate View dargestellt. Auf dieser Erkenntnis aufbauend, konzipieren wir sowohl die textbasierten Views, als auch die Views mit grafischer Oberfläche.

Schnittstellen entwerfen

Alle Views haben gemeinsame Eigenschaften. Die Anwendung unterscheidet zwei wesentliche Arten, die es zu unterteilen gilt. Neben den bereits genannten Views für die Spieler gibt es noch einen weiteren Typ von View, der beim Start der Anwendung zu sehen ist. Es handelt sich um das Hauptmenü. Das Hauptmenü gehört keinem Spieler, es repräsentiert allgemein die Anwendung und kann daher nicht in die Kategorie "Spieler" eingeordnet werden. Im Hauptmenü können Informationen geladen, Spiele gestartet und Lizeninformationen angezeigt werden.

Es ist sinnvoll mit der Schnittstelle für diesen elementaren Typ von View zu beginnen. In Battleship wird dazu das Interface ViewInterface definiert. Das Interface implementiert, wie es sich für eine View in MVC gehört, das Interface Observer. Es stellt alle wesentlichen Informationen zur Verfügung, die von einer Hauptpräsentation benötigt werden.

public interface ViewInterface extends Observer
{
    /**
     * Sets the model for this controller.
     *
     * @param model the model
     */
    void setModel(ModelInterface model);
 
    /**
     * Returns the model for this controller.
     *
     * @return the model
     */
    ModelInterface getModel();
 
    /**
     * Set the controller.
     *
     * @param controller    the controller
     */
    void setController(ControllerInterface controller);
 
    /**
     * Get the controller.
     *
     * @return  the controller
     */
    ControllerInterface getController();
 
    /**
     * Generates the view.
     *
     * @param time  expected creation time for the splash bar
     */
    void generateView(int time);
 
    /**
     * Simple abstraction method for displaying messages.
     *
     * @param message
     */
    void showMessage(String message);
 
    /**
     * Shows a message in a message dialog.
     *
     * @param parentComponent   determines the Frame in which the dialog is displayed
     * @param message   the Object to display
     * @param title the title string for the dialog
     * @param messageType   the type of message to be displayed
     * @param icon  an icon to display in the dialog
     */
    void showMessageDialog(Component parentComponent, Object message, String title,
            int messageType, Icon icon);
 
    /**
     * Shows a dialog requesting input from the user.
     *
     * @param parentComponent   the parent Component for the dialog
     * @param message   the Object to display
     * @param title the String to display in the dialog title bar
     * @param messageType   the type of message that is to be displayed
     * @param pattern   a regex pattern
     * @return  user input
     */
    String showInputDialog(Component parentComponent, Object message,
            String title, int messageType, String pattern);
 
    /**
     * Prompts a dialog and asks the user to choose one string.
     *
     * @param parentComponent   the parent Component for the dialog
     * @param message   the Object to display
     * @param title the String to display in the dialog title bar
     * @param messageType   the type of message that is to be displayed
     * @param icon  the Icon image to display
     * @param selectionValues   an array of Objects that gives the possible selections
     * @param initialSelectionValue   the value used to initialize the input field
     * @return  the entered string
     */
    Object showInputDialog(Component parentComponent, Object message, String title,
            int messageType, Icon icon, Object[] selectionValues, Object initialSelectionValue);
 
    /**
     * Brings up a dialog with a specified icon, where the number of choices is
     * determined by the optionType parameter.
     *
     * @param parentComponent   determines the Frame in which the dialog is displayed
     * @param message   the Object to display
     * @param title the title string for the dialog
     * @param optionType    an int designating the options available on the dialog
     * @param messageType   an int designating the kind of message this is
     * @param icon  the icon to display in the dialog
     * @return  an int indicating the option selected by the user
     */
    int showConfirmDialog(Component parentComponent, Object message, String title,
            int optionType, int messageType, Icon icon);
 
    /**
     * Brings up a dialog with a specified icon, where the initial choice is
     * determined by the initialValue parameter and the number of choices is
     * determined by the optionType parameter.
     *
     * @param parentComponent   determines the Frame  in which the dialog is displayed
     * @param message   the Object to display
     * @param title the title string for the dialog
     * @param optionType    an integer designating the options available on the dialog
     * @param messageType   an integer designating the kind of message this is
     * @param icon  the icon to display in the dialog
     * @param options   an array of objects indicating the possible choices the user can make
     * @param initialValue  methods; if null, options are determined by the Look and Feel
     * @return  an integer indicating the option chosen by user, or CLOSED_OPTION
     */
    int showOptionDialog(Component parentComponent, Object message, String title,
            int optionType, int messageType, Icon icon, Object[] options, Object initialValue);
 
    /**
     * Enables or disables this component, depending on the value of the
     * parameter b. An enabled component can respond to user input and
     * generate events. Components are enabled initially by default.
     *
     * @param b If true, this component is enabled; otherwise it is disabled
     */
    void setEnabled(boolean b);
 
    /**
     * Shows or hides this component depending on the value of parameter b.
     *
     * @param b if true, shows this component; otherwise, hides this component
     */
    void setVisible(boolean b);
 
    /**
     * Repaint the complete view.
     *
     * @param both  if true, redraw both boards
     */
    void repaint(boolean both);
 
    /**
     * Generate the first player.
     */
    void getLocalViews();
 
    /**
     * Generate remote views.
     */
    void getRemoteViews();
 
    /**
     * Get the board sizes.
     */
    Coordinate getBoardSize();
 
    /**
     * Shows the game license.
     */
    void showLicense();
 
    /**
     * About the game.
     */
    void showAbout();
 
    /**
     * Shows a manual with rules for the game.
     */
    void showHelp();
 
    /**
     * Exit game.
     */
    void showExit();
 
    /**
     * Load a game.
     */
    void loadGame();
 
    /**
     * Show game statistics.
     */
    void showStatistics();
}

Sie können einige wichtige Eigenschaften erkennen, die eine derartige View besitzen muss. Sie sollte in der Lage sein Texte auszugeben, eine Option zum Starten von neuen Spielen bereitstellen oder auch eine Option, sich die Lizenzdaten anzeigen zu lassen.

Mit der Definition der ersten Schnittstelle kann nun auch die zweite Schnittstelle definiert werden. Alle Spieler implementieren das sogenannte PlayerInterface. Dieses Interface stellt Methoden, wie manualSet zum automatischen Platzieren von Schiffen oder move zum Ausführen von Aktionen zur Verfügung.

public interface PlayerInterface extends Observer
{
    /**
     * Sets the model for this controller.
     *
     * @param model the model
     */
    void setModel(ModelInterface model);
 
    /**
     * Returns the model for this controller.
     *
     * @return the model
     */
    ModelInterface getModel();
 
    /**
     * Set the controller.
     *
     * @param controller    the controller
     */
    void setController(ControllerInterface controller);
 
    /**
     * Get the controller.
     *
     * @return  the controller
     */
    ControllerInterface getController();
 
    /**
     * Get the player id.
     *
     * @return  the player id
     */
    String getId();
 
    /**
     * Set the player id.
     *
     * @param id  the new player id
     */
    void setId(String id);
 
    /**
     * Enables or disables this component, depending on the value of the
     * parameter b. An enabled component can respond to user input and
     * generate events. Components are enabled initially by default.
     *
     * @param b If true, this component is enabled; otherwise it is disabled
     */
    void setEnabled(boolean b);
 
    /**
     * Shows or hides this component depending on the value of parameter b.
     *
     * @param b if true, shows this component; otherwise, hides this component
     */
    void setVisible(boolean b);
 
    /**
     * Set all ships manually.
     */
    void manualSet();
 
    /**
     * Set a ship.
     *
     * @param type      the ship type
     * @param coord     the left, highest coord of the ship
     * @param vertical  if true, the ship lies vertically
     * @return          true if successfull
     */
    boolean setShip(String type, Coordinate coord, boolean vertical);
 
    /**
     * Return the node id of the remote player.
     * 
     * @return  an unique network identifier
     */
    String getPid();
 
    /**
     * Set all ships on a board.
     */
    void setShips();
 
    /**
     * Set all ships on the board.
     * 
     * @param fleet a network stream with all ships
     * @return true, if successful 
     */    
    boolean setShips(String fleet);
 
    /**
     * Generates the view.
     */
    void generateView();
 
    /**
     * Simple abstraction method for displaying messages.
     *
     * @param message
     */
    void showMessage(String message);
 
    /**
     * Shows a message in a message dialog.
     *
     * @param parentComponent   determines the Frame in which the dialog is displayed
     * @param message   the Object to display
     * @param title the title string for the dialog
     * @param messageType   the type of message to be displayed
     * @param icon  an icon to display in the dialog
     */
    void showMessageDialog(Component parentComponent, Object message, String title,
            int messageType, Icon icon);
 
    /**
     * Shows a dialog requesting input from the user.
     *
     * @param parentComponent   the parent Component for the dialog
     * @param message   the Object to display
     * @param title the String to display in the dialog title bar
     * @param messageType   the type of message that is to be displayed
     * @param pattern   a regex pattern
     * @return  user input
     */
    String showInputDialog(Component parentComponent, Object message,
            String title, int messageType, String pattern);
 
    /**
     * Prompts a dialog and asks the user to choose one string.
     *
     * @param parentComponent   the parent Component for the dialog
     * @param message   the Object to display
     * @param title the String to display in the dialog title bar
     * @param messageType   the type of message that is to be displayed
     * @param icon  the Icon image to display
     * @param selectionValues   an array of Objects that gives the possible selections
     * @param initialSelectionValue   the value used to initialize the input field
     * @return  the entered string
     */
    Object showInputDialog(Component parentComponent, Object message, String title,
            int messageType, Icon icon, Object[] selectionValues, Object initialSelectionValue);
 
    /**
     * Brings up a dialog with a specified icon, where the number of choices is
     * determined by the optionType parameter.
     *
     * @param parentComponent   determines the Frame in which the dialog is displayed
     * @param message   the Object to display
     * @param title the title string for the dialog
     * @param optionType    an int designating the options available on the dialog
     * @param messageType   an int designating the kind of message this is
     * @param icon  the icon to display in the dialog
     * @return  an int indicating the option selected by the user
     */
    int showConfirmDialog(Component parentComponent, Object message, String title,
            int optionType, int messageType, Icon icon);
 
    /**
     * Brings up a dialog with a specified icon, where the initial choice is
     * determined by the initialValue parameter and the number of choices is
     * determined by the optionType parameter.
     *
     * @param parentComponent   determines the Frame  in which the dialog is displayed
     * @param message   the Object to display
     * @param title the title string for the dialog
     * @param optionType    an integer designating the options available on the dialog
     * @param messageType   an integer designating the kind of message this is
     * @param icon  the icon to display in the dialog
     * @param options   an array of objects indicating the possible choices the user can make
     * @param initialValue  methods; if null, options are determined by the Look and Feel
     * @return  an integer indicating the option chosen by user, or CLOSED_OPTION
     */
    int showOptionDialog(Component parentComponent, Object message, String title,
            int optionType, int messageType, Icon icon, Object[] options, Object initialValue);
 
    /**
     * Repaint view. If Coordinate is null, the complete board will be
     * redrawed.
     *
     * @param both  an internal flag
     */
    void repaint(boolean both);
 
    /**
     * Start game. Inform the user about it.
     */
    void start();
 
    /**
     * Performs a move.
     */
    void move();
 
    /**
     * Alywas called, when the player wins.
     */
    void won();
 
    /**
     * Called, when the player looses a game.
     */
    void lost();
 
    /**
     * Close view.
     */
    void close();
}

Mit diesen beiden Schnittstellen wurden die zwei Arten von Views hinreichend definiert. Als nächstes folgt die Implementierung der Views.

Konsole

Die Kommandozeile, Befehlszeile oder aus dem Englischen command-line interface, kurz CLI, oft auch als Konsole oder Terminal bezeichnet, ist ein Eingabebereich für die Steuerung einer Software, der typischerweise im Textmodus abläuft. Die Kommandos oder Befehle werden als Wörter eingegeben. Die Ausführung der Befehle wird meist direkt aus der Zeile durch zusätzlich angegebene Parameter gesteuert. Programme, die den Benutzer interaktiv befragen, sind auf dieser Ebene eher unüblich.

Ein Spiel in der Kommandozeile ist nicht ereignisgesteuert. Das bedeutet das Aktionen nicht vom Benutzer initiiert werden können, sondern vom Programm ausgehen. Das Programm fordert den Benutzer zu bestimmten Zeitpunkten auf, Eingaben zu tätigen, ganz im Gegensatz zu einer grafischen Oberfläche, bei der Aktionen beliebig vom Benutzer angestoßen werden können, z.B. durch einen Knopfdruck.

Bereits jetzt ist ersichtlich, dass ein Kommandozeilenprogramm auf einen Programmfluss angewiesen ist. Die Anwendung muss das Spiel lenken und den Benutzer explizit abfragen. Bei der Programmierung ist dies zu berücksichtigen.

Hauptoberfläche

Wir beginnen mit der View für die Hauptoberfläche und implementieren alle wichtigen Methoden. Im folgenden Quelltext ist die Klasse teilweise dargestellt.

public class MainConsoleView implements ViewInterface
{
    /**
     *
     * @param model the corresponding model
     * @param controller    the controller
     */
    public MainConsoleView(ModelInterface model, ControllerInterface controller)
    {
        // Set the model.
        setModel( model );
 
        // If a controller was supplied, use it. Otherwise let the first
        // call to getController() create the default controller.
        if( controller != null ) {
            setController( controller );
        }
 
        model.addObserver( this );    // We are the observer
    }
 
    /**
     * This method is called whenever the observed object is changed. The
     * observed object is the model.
     *
     * @param obj   the observable
     * @param arg   the retrieved arguments
     */
    @Override
    public void update(Observable obj, Object arg)
    {                
    }
 
    /**
     * Returns the default controller for this view.
     *
     * @param model the model
     * @return  the controller interface
     */
    public ControllerInterface defaultController(ModelInterface model)
    {
        return null;
    }
 
    /**
     * Sets the model this view is observing.
     *
     * @param model
     */
    @Override
    public void setModel(ModelInterface model)
    {
        model_ = model;
    }
 
    /**
     * Returns the model this view is observing.
     *
     * @return  the model
     */
    @Override
    public ModelInterface getModel()
    {
        return model_;
    }
 
    /**
     * Sets the controller for this view.
     *
     * @param controller
     */
    @Override
    public void setController(ControllerInterface controller)
    {
        controller_ = controller;
 
        // Tell the controller this object is its view.
        getController().setView( this );
    }
 
    /**
     * Returns this view's controller.
     *
     * @return  the controller interface
     */
    @Override
    public ControllerInterface getController()
    {
        // If a controller hasn't been defined yet...
        if (controller_ == null) {
            // ...make one. Note that defaultController() is normally overridden
            // by the AbstractView subclass so that it returns the appropriate
            // controller for the view.
            setController( defaultController( getModel() ) );
        }
 
        return controller_;
    }
}

Im Konstruktor wird die View mit den Schnittstellen vom Model und dem Controller instanziert. Mithilfe der Methoden setModel und setController werden das Model der View und der Controller gesetzt.
Abschließend registriert sich diese View beim Model als Beobachter. Dieses Verfahren gehört zu den Standardverfahren in MVC. Die Methode update bleibt von der MainConsoleView allerdings unbelegt.

Zu der Klasse MainConsoleView gehört die Methode generateView. Diese Methode generiert das Hauptmenü, das nach dem Start der Anwendung zu sehen ist.

/**
 * This method generates the main view when a game starts. It will
 * create the welcome screen.
 *
 * @param time  expected creation time for splash (not used in console mode)
 */
@Override
public void generateView(int time)
{
    showMessage( "\n" + model_.getString( "BATTLESHIP.INITIALIZE_GAME" ) + "\n\n" );
 
    // Create main menu
    Object[] possibleValues = {
        model_.getString( "BATTLESHIP.START_LOCAL_GAME" ),
        model_.getString( "BATTLESHIP.LOAD_GAME" ),
        model_.getString( "BATTLESHIP.START_REMOTE_GAME" ),
        model_.getString( "BATTLESHIP.LICENSE" ),
        model_.getString( "BATTLESHIP.AUTHOR" ),
        model_.getString( "BATTLESHIP.CHECK_FOR_UPDATES" ),
        model_.getString( "BATTLESHIP.END_GAME" )
    };
 
    // Loop until the player exits the game
    while( true ) {
 
        // Print ascii art
        showMessage( model_.getString( "BATTLESHIP.ASCII_LOGO" ) );
 
        Object input = showInputDialog( null,
                model_.getString( "BATTLESHIP.CHOOSE_OPTION" ),
                model_.getString( "BATTLESHIP.CHOOSE" ),
                0, null, possibleValues, possibleValues[0] );
 
        if( input.equals( possibleValues[0] ) ) {
            // Start new local game
            controller_.localGame();
        } else if( input.equals( possibleValues[1] ) ) {
            // Load an old game
            controller_.load();
        } else if( input.equals( possibleValues[2] ) ) {
            // Start new remote game
            controller_.remoteGame();
        } else if( input.equals( possibleValues[3] ) ) {
            // Print copyright notice
            controller_.license();
        } else if( input.equals( possibleValues[4] ) ) {
            // Print help
            controller_.about();
        } else if( input.equals( possibleValues[5] ) ) {
            // Check for updates
            controller_.checkForUpdates( this );
        } else if( input.equals( possibleValues[6] ) ) {
            // Exits the menu, leaves the game
            controller_.exit();
        }
    }
}

Darüberhinaus implementiert die Klasse alle weiteren Methoden aus dem Interface ViewInterface. Eine davon ist showLicense, welche die Lizenzinformationen für das Spiel auf die Kommandozeile druckt.

/**
 * Shows the game license.
 *
 * @see <a href="www.gnu.org/copyleft/gpl.html">GNU General Public License</a>
 */
@Override
public void showLicense()
{
    // Path to the license file
    String license = model_.getString( "BATTLESHIP.PATH_TO_LICENSE_FILE" );
 
    // Display the copyright license
    showMessage( IOUtil.resToStrBuilder( license ).toString() );
}
Spieleroberfläche

Jeder gewöhnliche Spieler in Battleship muss das PlayerInterface implementieren. Das gilt auch für die Kommandozeilenansicht eines menschlichen Spielers. Die Klasse HumanConsoleView repräsentiert einen menschlichen Konsolenspieler. Ein Konsolenspieler muss in der Lage sein Befehle über die Kommandozeile einzugeben. Desweiteren kann ein menschlicher Spieler seine Spielfelder textbasiert einsehen. Die Klasse HumanConsoleView formatiert alle Daten aus dem Model und stellt sie in der Kommandozeile dem Benutzer visuell dar.

Der Konstruktor in HumanConsoleView gleicht dem bereits bekannten Konstruktor der Klasse MainConsoleView mit Ausnahme von einer Zeile. Unmittelbar nach dem Setzen des Models wird auch die Identifikationsnummer für den Spieler generiert. Dazu wird die Methode getIdentification über den Controller aufgerufen. Die ID wird einmalig zugewiesen und verändert sich nicht mehr. Sie ist der Schlüssel, mit dem der Spieler (View) seine Daten im Model abrufen kann.

/**
 * Constructs the human player.
 * 
 * @param model the corresponding model
 * @param controller    the controller
 */
public HumanConsoleView(ModelInterface model, ControllerInterface controller)
{
    // Set the model.
    setModel( model );
 
    // On startup create profile and get an id
    setId( controller.getIdentification( this ) );
 
    // If a controller was supplied, use it. Otherwise let the first
    // call to getController() create the default controller.
    if( controller != null ) {
        setController( controller );
    }
 
    // Let us add as an observer to the model
    model.addObserver( this );
}

Mithilfe des Observer Entwurfsmusters wird ein Spieler bei Änderungen, z.B. Schüssen auf das eigene oder gegnerische Spielfeld vom Model über die Methode update umgehend unterrichtet.

/**
 * This method is called whenever the observed object is changed. The
 * observed object is the model.
 * 
 * @param obj   the observable object
 * @param arg   arguments
 */
@Override
public void update(Observable obj, Object arg)
{
    // Check if this view is active, if not return
    if( !enabled_ )
        return;
 
    // Check if the model tells us something. If not than we should
    // probably just redraw our boards.
    if( arg instanceof String) {
        showMessage( (String)arg ); // Display the message
    } else {        
        repaint( true );
    }       
}

Die Methode update ruft unmittelbar die Methode repaint auf. Diese Methode zeichnet die Spielfelder in der Kommandozeile neu, indem sie die aktuellen Daten aus dem Model liest.

Ein menschlicher Spieler muss ebenfalls die Methode move implementieren. Diese Methode wird vom Controller aufgerufen und teilt dem Benutzer mit, seinen nächsten Zug durchzuführen. In der Konsole wird dazu eine Eingabe abverlangt. Diese Eingabe wird anschließend ausgewertet und an einen bestimmten Handler übergeben, der vom Controller abgearbeitet wird. Die Eingabe ist mithilfe von regulären Ausdrücken (Regex) explizit eingegrenzt, so dass fehlerhafte Eingaben automatisch unterbunden werden.

/**
 * Let the player move.
 */
@Override
public void move()
{
    if( model_.isActivePlayer( getId() ) ) {
 
        showMessage( "\n" + getModel().getString( "BATTLESHIP.YOUR_TURN" ) + "\n" );
 
        showMessage( "\n" + getModel().getString( "BATTLESHIP.ENTER_COMMAND" ) );
 
        // Get a specified pattern. If you change this pattern, you probably
        // also have to change the parsing of the command line string.
        responseHandler( getInput("(([alehqrv]{1,1})?|([msu]{1,1}[0-9]{4,4})?|([@]{1,1}(.*))?)") );
    }
}

Die Klasse HumanConsoleView implementiert darüberhinaus auch noch die Methoden setShips zum Platzieren der eigenen Schiffe, sowie won. Diese Methode wird vom Controller aufgerufen, sobald der Spieler sein Spiel gewonnen hat.

Graphical User Interface (GUI)

Ein Spiel über die Kommandozeile zu spielen mag kurzfristig eine gewisse Abwechslung mit sich bringen. Sofern das aber nicht zwingend notwendig ist, z.B. weil das eigene System nur textbasiert arbeitet, ist es weitaus unterhaltsamer, wenn eine grafische Oberfläche zu sehen ist. Eine grafische Benutzeroberfläche ist eine Software-Komponente, die dem Benutzer eines Computers die Interaktion mit der Maschine über grafische Symbole erlaubt. Die Darstellungen und Elemente können unter Verwendung einer Maus gesteuert werden.

Das Model-View-Controller Architekturmuster gestattet es die View, also die Präsentationsschicht, völlig frei zu konzipieren. Neben den bereits implementierten Views für die Kommandozeile können wir nun zusätzlich weitere Views entwerfen, diesmal mit einer grafischen Benutzeroberfläche (GUI), so wie Sie es von anderen Anwendungen in der Regel gewohnt sind.

Hauptoberfläche

Wir beginnen an dieser Stelle ebenfalls bei der Hauptoberfläche. Die Klasse MainGuiView repräsentiert ein Hauptmenü mit klassischer grafischer Oberfläche. Die Klasse implementiert ebenfalls das Interface ViewInterface. Bei der Klasse MainGuiView handelt es sich um eine Subklasse von javax.swing.JFrame. Alle Methoden von JFrame werden mittels Vererbung an die Klasse MainGuiView weitergegeben.

// Teilauszug aus der Klasse MainGuiView
public class MainGuiView extends JFrame implements ViewInterface
{
    /** 
     * Creates new form GuiApplication.
     *
     * @param model the corresponding model
     * @param controller    the controller
     */
    public MainGuiView(ModelInterface model, ControllerInterface controller)
    {
        // Set the model.
        setModel( model );
 
        // If a controller was supplied, use it. Otherwise let the first
        // call to getController() create the default controller.
        if( controller != null ) {
            setController( controller );
        }
 
        // Add observer to the model
        model_.addObserver( this );
 
        // Set java's look & feel
        LAFSettings.setNativeLookAndFeel();
    }
 
    /**
     * This is the main build method for the view. Add your code here.
     *
     * @param time  expected creation time for the splash bar
     */
    @Override
    public void generateView(int time)
    {
        // Create the splash screen
        Splash splash = new Splash( time );
 
        // 1. Initialize gui
        splash.drawSplashOperation( model_.getString( "BATTLESHIP.INITIALIZE_GUI" ) );
 
        initComponents();
        initMyComponents();
 
        try {
            Thread.sleep( 500 );
        } catch(InterruptedException ex) {
        }
 
        splash.drawSplashProgress();
 
        // 2. Generate directories
        splash.drawSplashOperation( model_.getString( "BATTLESHIP.INITIALIZE_ENVIRONMENT" ) );
 
        try {
            Thread.sleep( 1000 );
        } catch(InterruptedException ex) {
        }
 
        splash.drawSplashProgress();
 
        // 3. Initialize game
        splash.drawSplashOperation( model_.getString( "BATTLESHIP.INITIALIZE_GAME" ) );
 
        try {
            Thread.sleep( 500 );
        } catch(InterruptedException ex) {
        }
 
        splash.drawSplashProgress();
 
        splash.closeSplash();
 
        // Set the window position
        Point pos = new Point();
        pos.x = 100;
        pos.y = 100;
 
        setLocation(pos);
 
        // Make the view visible. Do not mess with Swing components in the
        // main thread after this call or pack() has been called.
        setVisible( true );
    }
}

Auch die Klasse MainGuiView implementiert, wie ihr Kollege in der Kommandozeile, die Methode generateView. Diese Methode erzeugt allerdings einen SplashScreen, generiert mit den Methoden initComponents() und initMyComponents() alle Buttons, Labels und andere grafischen Komponenten. Im Konstruktor wird mithilfe der Hilfsklasse LAFSettings das native „Look and Feel“ gewählt. Look and Feel (LAF; dt. Aussehen und Handhabung, „Anfühlen“, Anmutung) bezeichnet meist durch Hersteller oder Konsortien standardisierte Design-Aspekte einer Software, wie zum Beispiel Farben, Layout, Fontgröße und die Benutzung von grafischen Elementen (widgets).

Spieleroberfläche

Ein menschlicher Spieler hat bei einer grafischen Oberfläche in der Regel deutlich mehr Möglichkeiten. Der Hauptbestandteil des Quelltextes in einer grafischen View geht auf die Generierung der Schaltoberflächen und Menüs zurück. Auch die Ereignishandler müssen alle geschrieben werden.

Die Klasse HumanGuiView implementiert, wie alle anderen Views auch, die Methode update. Bei Änderungen von Daten benachrichtigt das Model alle Views.

/**
 * This method is called whenever the observed object is changed. The
 * observed object is the model.
 * 
 * @param obj   the observable object
 * @param arg   the argument
 */
@Override
public void update(Observable obj, Object arg)
{
    if( arg instanceof ModelMessage ) {
        ModelMessage msg = (ModelMessage)arg;
        if( msg.getType().equals( ModelMessage.MessageType.UPDATE ) ) {
            repaint( true );
        } else if( msg.getType().equals( ModelMessage.MessageType.NETWORKUPDATE ) ) {
            // State changes in the model, update all relevant gui data
            EventQueue.invokeLater( new Runnable() {
                @Override
                public void run()
                {
                    // Update user list
                    updateUserList();
                }
            });
 
            // Check for relevant messages
            checkForNewGames();
            checkForAcceptedGames();
        }
    }
}

An dieser Stelle wird ein weiterer elementarer Vorteil von MVC ersichtlich. Sobald sich neue User im Netzwerk einloggen, benachrichtigt das Model die View. Diese kann anschließend über EventQueue.invokeLater die Oberfläche mit der Methode updateUserList aktualisieren und zeigt neue Netzwerkteilnehmer umgehend in der Nutzerliste an. Dies geschieht vollkommen automatisch und beinahe in Echtzeit. Die View muss das Model nicht mühselig in einem Zyklus abfragen, ob sich die Daten geändert haben, um anschließend über einen Swing-Timer die Oberfläche zu aktualisieren.

Diese Ansatz, auch Polling (zyklische Abfrage) genannt, verbraucht unnötig Leistung und verlangsamt das Programm. Sehr bald schon hätten Sie darüberhinaus etliche Timer, die in zahlreichen Threads das Model mit Anfragen bombardieren. Das Observer Entwurfsmuster erspart uns dieses ineffiziente pollen. Die View wird nur dann aktualisiert, wenn sich tatsächlich auch Daten geändert haben. Das Model, das die Daten verwaltet, benachrichtigt uns sobald das der Fall ist.

In der Methode repaint werden, wie in der Klasse HumanConsoleView auch, die beiden Spielfelder bei Änderungen neu gezeichnet. In der grafischen Oberfläche werden dazu einfach die einzelnen Felder aktualisiert und neue Icons geladen.

/**
 * Repaint view. If Coordinate is null, the complete board will be
 * redrawed.
 *
 * @param both  if true, the home field will be repainted, if false the enemy
 */
@Override
public void repaint(boolean both)
{
    // This can only mean that the model has updated the data.
    // Draw the new board.
    Coordinate size = getModel().getBoardSize( getId() );
 
    for(int y = 0; y <= size.getY(); y++) {
        for(int x = 0; x <= size.getX(); x++) {
            jLabelHomeBoard_[x + (y * 10)].setIcon(
                    interpretFieldState(
                    getModel().getHomeBoardFieldState( getId(), new Coordinate( x, y ) ) ));
            try {
                jLabelEnemyBoard_[x + (y * 10)].setIcon(
                        interpretFieldState(
                        getModel().getEnemyBoardFieldState( getId(), new Coordinate( x, y ) ) ));
            } catch(FieldOperationException e) {
                showMessage( e.getMessage() );
            }
        }
    }
}

In dem Package eu.codeplanet.battleship.view.gui sind noch weitere View-Klassen für die GUI definiert. Dazu zählen triviale Fenster, wie die AboutView oder auch die ShipSetView. Letztere stellt ein Menü zur Schiffsplatzierung zur Verfügung. Auch das Chatfenster ist in diesem Unterpaket zu finden.



Zuletzt aktualisiert am Montag, den 23. April 2012 um 12:59 Uhr
 
AUSWAHLMENÜ