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 - Controller Drucken E-Mail
Benutzerbewertung: / 106
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

Controller

Die View und der Controller implementieren das klassische Strategy-Muster: Die View ist ein Objekt, das mit einer Strategie konfiguriert ist; der Controller liefert diese Strategie. Die View ist nur für die visuellen Aspekte der Anwendung zuständig; alle Entscheidungen über das Verhalten der Schnittstelle delegiert es an den Controller. Durch die Verwendung des Strategy-Musters bleibt die View außerhalb entkoppelt vom Model, denn es ist der Controller, der bei der Bearbeitung von Benutzereingaben für die Interaktion mit dem Model zuständig ist. Die View weiß nichts darüber, wie das vor sich geht.

In dem Spiel Battleship gibt es nur einen Controller. Die Klasse BattleshipController erweitert die abstrakte Klasse AbstractController und stellt alle Methoden über das Interface ControllerInterface bereit.
Der Controller steuert den kompletten Spielfluss und vermittelt zwischen Model und View.

Neben den Methoden localGame und connect, finden sich im Controller viele weitere Methoden zur Steuerung des Spiels. Eine bekannte Methode, die Sie bereits in den Views sehen konnten ist die Methode shoot. Diese Methode führt bei einem Schuss alle wesentlichen Flußsteuerungen aus und ruft anschließend die entsprechende View wieder auf.

/**
 * Shoots on a fields on the enemy board.
 *
 * @param player
 * @param c
 */
@Override
public void shoot(PlayerInterface player, Coordinate c)
{
    try {
        // Call model
        if( getModel().shoot( player.getId(), c ) ) {
            // If successful, check if it was the last ship and if
            // the game is over and inform the player
            player.showMessage( getModel().getString( "BATTLESHIP.STRIKE" ) );
            if( getModel().isGameOver( player.getId() ) ) {
                // Proceed with the victory steps
                processVictory( player );
            } else {
                // Game not over, move on
                player.move();
            }
        } else {
            // If not successful, toggle player and let him move
            getActivePlayer().move();
        }
    } catch(FieldOperationException e) {
        // Coords are not a valid field
        player.showMessage( e.getMessage() );
        // Try again
        player.move();
    } catch(InvalidShotException e) {
        // Shot wasn't valid
        player.showMessage( e.getMessage() );
        // Try again
        player.move();
    }
}

Der Controller generiert auch die Hauptoberfläche und hält einen Verweis auf ihre Instanz. Eine weitere Methode im Controller ist die Methode checkForUpdates. Diese Methode ruft eine Instanz der Klasse VersionManager auf.

/**
 * Checks if updates are available.
 *
 * @param view  the view called
 */
@Override
public void checkForUpdates(ViewInterface view)
{
    VersionManager manager = new VersionManager( getModel(), view );
    manager.checkUpdateCheck( 3 ); // Perform update
}

Der VersionManager stellt eine Onlineverbindung mithilfe der Java-Klasse URLConnection her und überprüft ob es eine aktuellere Version von Battleship gibt, als die vom Benutzer gerade verwendete. Dazu wird eine Seite von Codeplanet kontaktiert, die mithilfe eines PHP-Skriptes die aktuelle Version übermittelt.

Die Seite kann testweise über http://www.codeplanet.eu/files/battleship/version.php?version=1.0.0&language=de_DE aufgerufen werden.

Findet das Programm eine aktuellere Version, wird der Nutzer über den installierten Systembrowser automatisch zum Installer der neuen Version weitergeleitet. Das Datum der letzten Aktualisierung wird in der Konfigurationsdatei gespeichert. Das Programm gestattet Aktualisierungen nur in bestimmten Zeitintervallen um ein Flooding der angegebenen Seite zu verhindern.

/**
 * Checks for a new battleship version. This method will open
 * an URLConnection to a remote server to check for program updates.
 * If any updates are available, it will redirect the user browser
 * to the installer package.
 *
 * @param notifyNoUpdate    if true, the user gets detailled informations
 * @return  true, if successful
 */
public boolean checkForNewVersion(boolean notifyNoUpdate)
{
    URL url = null;
 
    try {
        String base = "http://www.codeplanet.eu/files/battleship/version.php";
        String version = "?version=" + Version.getShortVersion();
        String language = "&language=" + model_.getConfig().getPreferences().getLocale();
        String address = base + version + language;
        url = new URL( address );
    } catch( MalformedURLException e ) {
        logger_.warning( model_.getString( "BATTLESHIP.UNABLE_TO_CHECK_FOR_UPDATED_VERSION" ) +
                "\n" + e.getMessage() );
 
        if( notifyNoUpdate ) {
            view_.showMessageDialog( null, e.getMessage(),
                    model_.getString( "BATTLESHIP.UNABLE_TO_CHECK_FOR_UPDATED_VERSION" ),
                    0, null );
        }
 
        return false;
    }
 
    InputStream inputStream = null;
    InputStreamReader inputStreamReader = null;
    BufferedReader reader = null;
 
    // Try to get connection
    try {
        URLConnection urlConnection = url.openConnection();
 
        urlConnection.setConnectTimeout( 5000 );
 
        if( urlConnection instanceof HttpURLConnection ) {
            logger_.info( model_.getString( "BATTLESHIP.UPDATE_CHECK_WITH_HTTP" ) );
            HttpURLConnection conn = (HttpURLConnection)urlConnection;
 
            if( conn.usingProxy() ) {
                logger_.info( model_.getString( "BATTLESHIP.HTTP_USING_PROXY" ) );
            }
        }
 
        inputStream = urlConnection.getInputStream();
        inputStreamReader = new InputStreamReader( inputStream );
        reader = new BufferedReader( inputStreamReader );
 
        String newVersLine = reader.readLine();
        String curVersLine = Version.getShortVersion();
 
        boolean newVersionAvailable = false;
 
        if( newVersLine != null && curVersLine != null ) {
 
            String newVersion = newVersLine;
 
            if( newVersion.compareTo(curVersLine) > 0 ) {
                newVersionAvailable = true;
 
                String[] filler = { newVersion };
 
                if ( view_.showConfirmDialog( null,
                        model_.getString( "BATTLESHIP.NEW_VERSION_AVAILABLE_DO_DOWNLOAD", filler ),
                        model_.getString( "BATTLESHIP.NEW_VERSION_AVAILABLE", filler ),
                        JOptionPane.YES_NO_OPTION, JOptionPane.PLAIN_MESSAGE, null)
                    == JOptionPane.YES_OPTION ) {
 
                    // Try to download installer
                    try {
                        BrowserManager.openURL( INSTALL_URL );
                    } catch( IOException e ) {
                        view_.showMessageDialog( null, e.getMessage(),
                            model_.getString( "BATTLESHIP.UNABLE_TO_OPEN_BROWSER" ), 0, null );
                    }
                }
            }
        }
 
        if( !newVersionAvailable && notifyNoUpdate ) {
 
            String[] filler = { Version.getShortVersion() };
 
            view_.showMessageDialog( null,
                    model_.getString( "BATTLESHIP.LATEST_VERSION_INSTALLED", filler ),
                    model_.getString( "BATTLESHIP.NO_UPDATE_AVAILABLE" ),
                    JOptionPane.INFORMATION_MESSAGE, null );
        }
 
    } catch( IOException e ) {
        logger_.warning(  model_.getString( "BATTLESHIP.UNABLE_TO_CHECK_FOR_UPDATED_VERSION" ) +
                " " + e.getMessage() );
 
        if( notifyNoUpdate ) {
            view_.showMessageDialog( null, e.getMessage(),
                    model_.getString( "BATTLESHIP.UNABLE_TO_CHECK_FOR_UPDATED_VERSION" ),
                    JOptionPane.INFORMATION_MESSAGE, null );
        }
 
        return false;
 
    } finally {
        if( inputStream != null ) {
            try {
                inputStream.close();
            } catch( IOException e ) {
            }
        }
 
        if( inputStreamReader != null ) {
            try {
                inputStreamReader.close();
            } catch( IOException e ) {
            }
        }
 
        if( reader != null ) {
            try {
                reader.close();
            } catch( IOException e ) {
            }
        }
    }
 
    return true;
}

Das Spiel

Zum Abschluß wollen wir einen Blick auf die Konstruktion des Spiels werfen. In der Klasse Main wird über Reflection die Basisklasse Game aufgerufen.

public class Main 
{
    /**
     * For now, fire off the Main class.
     * In the future, this class may do some logic to find out available
     * startup classes and pick one (like the uis one does).
     * 
     * @param args  arguments
     */
    public static void main(String[] args) throws IOException
    {
        try {
 
            Class startupClass = null;
 
            startupClass = Class.forName("eu.codeplanet.battleship.core.Game");
 
            final Constructor constructor = startupClass.getConstructor(new Class[] {
                String[].class
            });
 
            constructor.newInstance(new Object[] { args });
 
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

Die Instanzierung beginnt mit der Konstruktion des Models. Unmittelbar nach der Konstruktion wird die essentielle Methode initialize aufgerufen, die das Model initialisiert. Schlägt die Initialisierung aus irgendeinem Grund fehl, wird sofort abgebrochen.

Nachdem das Model erfolgreich initialisiert wurde, wird der Controller instanziert. Diesem werden die Kommandozeilenargumente übergeben.

/**
 * This class will represent the start point for the console application.
 * We are using the MVC-Pattern and first generate a singleton model and then
 * pass it to the main controller.
 * 
 * <pre>
 *               +------------+
 *               |   Model    |
 *               +------------+
 *              /\ .          /\
 *              / .            \
 *             / .              \
 *            / .                \
 *           / \/                 \
 *    +------------+ <------ +------------+
 *    |    View    |         | Controller |
 *    +------------+ ......> +------------+
 * </pre>
 * 
 * @author  CodePlanet
 * @version 1.0.0, 04/06/2009
 * @see     <a href="http://www.codeplanet.eu/">http://www.codeplanet.eu/</a> 
 */
public class Game
{
    public Game(final String args[])
    {
        // The preferred way to transfer control and begin working with Swing
        // is to use invokeLater.
        Runnable init = new Runnable() {
            @Override
            public void run()
            {
                // Instantiate the model.
                final BattleshipModel model = BattleshipModel.getInstance();
 
                // Initialize model.
                if( model.initialize() == false ) {
                    System.exit( -1 );  // Major failure, shutdown
                }
 
                // In this line, the view gets instantiated by the controller, too.
                final ControllerInterface controller = new BattleshipController( model, args );
            }
        };
 
        java.awt.EventQueue.invokeLater( init );
    }
}

Mithilfe der Kommandozeilenargumente kann das Spiel über die Konsole gestartet werden. Dazu wird die folgende Syntax verwendet.

java -splash:null -Dfile.encoding=cp850 -jar "C:\Battleship.jar" console

Das Spiel kann durch den Doppelkick auf das Jar-Archiv auch automatisch gestartet werden. Allerdings startet es in diesem Fall direkt im Modus GUI mit grafischer Benutzeroberfläche.

Javadoc

Für das gesamte Projekt Battleship steht eine ausführliche Dokumentation zur Verfügung, die mit Javadoc generiert wurde. Javadoc ist ein Software-Dokumentationswerkzeug, das aus Java-Quelltexten automatisch HTML-Dokumentationsdateien erstellt. Die Daten für Battleship stehen unter http://codeplanet.eu/files/battleship/javadoc/ zur Ansicht bereit.

Sie können sich die HTML-Dokumentationsdateien näher ansehen. Dort erfahren Sie mehr über den Aufbau der Pakete und Klassen. Sie erhalten einen Überblick über die Methoden einer Klasse und ihren Zweck.

In der nachfolgenden Abbildung wurde das komplette Projekt mit einem Tool gemessen. Es zeigt die totale Anzahl an Klassen, Kommentarzeilen und Quelltextzeilen.

SLOC
Abbildung 3: Messung der »Source lines of code (SLOC)«

Schluss

Wir sind am Ende unseres Artikels angekommen. Sie haben Methoden und Wege kennengelernt, wie sich kleine Softwareprojekte planen und durchführen lassen. Sie haben wichtige Entwurfs- und Architekturmuster kennengelernt, eine Netzwerkbibliothek programmiert und sich mit einigen interessanten Algorithmen vertraut gemacht.

Dieser Artikel wurde fertiggestellt, als mit Version 1.0.0 die erste Betaversion von Battleship herausgegeben wurde. Die Betaversion ist bereits in großen Teilen einsetzbar. Einige Funktionen wurden in dieser Version allerdings noch nicht eingeführt und sind für spätere Versionen geplant. Dies betrifft nicht alle in diesem Tutorial gestellten Anforderungen. Diese sind bereits vollständig implementiert und funktionsfähig.

Das Projekt Battleship enthält noch viele weitere interessante Klassen, die Sie sich im Anhang näher ansehen können. Im Quelltext erfahren Sie wie Dokumente für den Chat serialisiert werden, bevor man sie über ein Netzwerk überträgt, wie sich Popupfenster in Java erstellen lassen, wie man seine eigenen Renderer programmiert und wie Ressourcen verwaltet werden. Darüberhinaus finden Sie viele nützliche Funktionen, die Sie in ihren Java-Programmen weiterverwenden können. Neben der sehr flexiblem Netzwerkbibliothek sind das auch kurze Codesnippets, z.B. zur Erzeugung von MD5- oder SHA-Hashwerten oder Methoden zur Manipulation von Bits und Bytes.

Wir hoffen Sie hatten Spaß beim Lesen dieses Artikels, haben einige neue Informationen gewinnen können und wünschen Ihnen viel Spass beim Nachprogrammieren.

Ihr CodePlanet Team.



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