org.javassonne.ui.controllers.GameController.java Source code

Java tutorial

Introduction

Here is the source code for org.javassonne.ui.controllers.GameController.java

Source

/**
 * Javassonne 
 *  http://code.google.com/p/javassonne/
 * 
 * @author [Add Name Here]
 * @date Jan 29, 2009
 * 
 * Copyright 2009 Javassonne Team
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License. 
 *  You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
 *  Unless required by applicable law or agreed to in writing, software 
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
 *  implied. See the License for the specific language governing 
 *  permissions and limitations under the License. 
 */

package org.javassonne.ui.controllers;

import java.awt.Point;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.javassonne.algorithms.RegionsCalc;
import org.javassonne.logger.LogSender;
import org.javassonne.messaging.Notification;
import org.javassonne.messaging.NotificationManager;
import org.javassonne.model.Meeple;
import org.javassonne.model.Player;
import org.javassonne.model.Tile;
import org.javassonne.model.TileBoard;
import org.javassonne.model.TileBoardGenIterator;
import org.javassonne.model.TileBoardIterator;
import org.javassonne.model.TileDeck;
import org.javassonne.model.TileMapBoard;
import org.javassonne.model.TileSerializer;
import org.javassonne.model.TileSet;
import org.javassonne.networking.LocalHost;
import org.javassonne.networking.impl.JmDNSSingleton;
import org.javassonne.networking.impl.RemotingUtils;
import org.javassonne.networking.impl.ThreadPool;
import org.javassonne.ui.DisplayHelper;
import org.javassonne.ui.GameState;
import org.javassonne.ui.GameState.Mode;
import org.javassonne.ui.controls.JPopUp;
import org.javassonne.ui.map.MeepleSprite;
import org.javassonne.ui.panels.InputPlayerDataPanel;
import org.javassonne.ui.panels.InstructionsPanel;
import org.javassonne.ui.panels.MenuPanel;
import org.javassonne.ui.panels.ViewNetworkHostsPanel;

import com.thoughtworks.xstream.XStream;

/**
 * The GameController is the primary controller in the application. It is
 * created from the Main function and handles broad events - such as displaying
 * menus, starting new games, etc.. When a game starts, it creates
 * "subcontrollers" to handle the heavy lifting involved in the user interface,
 * etc..
 * 
 * @author bengotow
 */
public class GameController {

    private static final String END_WITHOUT_SAVING = "End Without Saving";

    private static final String RETURN_TO_GAME = "Return to Game";

    private static final long serialVersionUID = 1L;

    private BoardController boardController_;
    private HUDController hudController_;

    private InstructionsPanel instructions_;
    private MenuPanel menu_;

    /**
     * The default constructor takes no arguments and assumes a GameWindow has
     * been created and is onscreen. It adds itself to the notification manager
     * and waits for the user to click something in the menu.
     */
    public GameController() {

        // Turn off annoying RMI logging
        Logger.getLogger("org.springframework").setLevel(Level.SEVERE);

        // Does nothing but force the LocalHost to
        // be created sometime soon
        LocalHost.getName();

        // create the menu panel
        menu_ = new MenuPanel();
        instructions_ = new InstructionsPanel();

        // register to receive events from the game window
        NotificationManager n = NotificationManager.getInstance();
        n.addObserver(Notification.NEW_GAME, this, "newGame");
        n.addObserver(Notification.NEW_NW_GAME, this, "newNetworkGame");
        n.addObserver(Notification.START_GAME, this, "startGame");
        n.addObserver(Notification.ATTEMPT_END_GAME, this, "attemptEndGame");
        n.addObserver(Notification.END_GAME, this, "endGame");
        n.addObserver(Notification.LOAD_GAME, this, "loadGame");
        n.addObserver(Notification.SAVE_GAME, this, "saveGame");
        n.addObserver(Notification.SCORE_TURN, this, "scoreTurn");
        n.addObserver(Notification.QUIT, this, "quitGame");
        n.addObserver(Notification.TOGGLE_MAIN_MENU, this, "toggleMainMenu");
        n.addObserver(Notification.TILE_UNUSABLE, this, "beginTurn");
        n.addObserver(Notification.BEGIN_TURN, this, "beginTurn");
        n.addObserver(Notification.TOGGLE_INSTRUCTIONS, this, "toggleInstructions");
        n.addObserver(Notification.START_NETWORK_GAME, this, "startGameAsNetworkClient");
    }

    /**
     * Called when a NEW_GAME notification is received.
     * 
     * @param n
     *            The notification object sent from the NotificationManager.
     */
    public void newGame(Notification n) {

        InputPlayerDataPanel p = new InputPlayerDataPanel();
        DisplayHelper.getInstance().add(p, DisplayHelper.Layer.MODAL, DisplayHelper.Positioning.CENTER);
    }

    /**
     * Called when a NEW_NW_GAME notification is received.
     * 
     * @param n
     *            The notification object sent from the NotificationManager.
     */
    public void newNetworkGame(Notification n) {

        // Client mainClient = new Client("mainClient");

        ViewNetworkHostsPanel p = new ViewNetworkHostsPanel();
        DisplayHelper.getInstance().add(p, DisplayHelper.Layer.MODAL, DisplayHelper.Positioning.CENTER);
    }

    /**
     * Called when a START_GAME notification is received.
     * 
     * @param n
     *            The notification object sent from the NotificationManager.
     */
    public void startGame(Notification n) {
        // Game in progress!
        ArrayList<Player> players_ = (ArrayList<Player>) n.argument();
        GameState.getInstance().startGameWithPlayers(players_);

        NotificationManager.getInstance().sendNotification(Notification.TOGGLE_MAIN_MENU);

        // Load all possible tiles
        TileSerializer s = new TileSerializer();
        TileSet set = s.loadTileSet("tilesets/standard.xml");
        if (set == null) {
            System.err.println("Tile set could not be found.");
            System.exit(0);
        }

        // Populate the set into the deck. Note: you can have multiple copies of
        // a tile in a deck, but not in a set
        TileDeck deck = new TileDeck();
        deck.addTileSet(set);
        // Uncomment to Test the Game Over functionality
        // while (deck.tilesRemaining() > 10)
        // deck.popRandomTile();

        GameState.getInstance().setDeck(deck);

        TileBoard board = new TileMapBoard();
        GameState.getInstance().setBoard(board);
        GameState.getInstance().setMode(GameState.Mode.PLAYING_LOCAL_GAME);

        // Create a BoardController to do the heavy lifting during gameplay.
        // These two objects handle notifications from the UI (like rotate
        // tile).
        boardController_ = new BoardController();
        hudController_ = new HUDController();

        // See if the first person is playing on this computer. If they are,
        // send the begin turn notification to activate the interface for
        // them.
        beginTurn();
    }

    public void startGameAsNetworkClient(Notification n) {
        if (GameState.getInstance().getMode() != Mode.IN_LOBBY)
            return;

        HashMap<String, Object> gameData = (HashMap<String, Object>) n.argument();

        GameState.getInstance().setDeck((TileDeck) gameData.get("deck"));

        TileBoard board = (TileBoard) gameData.get("board");
        board.removeTemps();
        GameState.getInstance().setBoard(board);

        ArrayList<Player> players = (ArrayList<Player>) gameData.get("players");

        // close the main menu
        menu_.close();

        // start the game!
        GameState.getInstance().setMode(Mode.PLAYING_NW_GAME);
        GameState.getInstance().startGameWithPlayers(players);

        boardController_ = new BoardController();
        hudController_ = new HUDController();

        beginTurn();
    }

    /**
     * Called when a turn is ended, and may be called again if the
     * BoardController finds the drawn tile unusable.
     * 
     */
    public void beginTurn() {
        // if the current player is playing on this machine, we need to enable
        // the interface so they can place a tile. We do that by passing another
        // notification
        Player p = GameState.getInstance().getCurrentPlayer();

        if (p.getIsLocal() == true) {
            // Draw the another tile!
            TileDeck d = GameState.getInstance().getDeck();
            Tile t = d.popRandomTile();
            GameState.getInstance().setDeck(d);

            // Send notifications to attach our tileInHand to the view
            GameState.getInstance().setTileInHand(t);
        }

        // Toggle an update of the score board
        NotificationManager.getInstance().sendNotification(Notification.SCORE_UPDATE);
    }

    /**
     * Called when an END_GAME notification is received. This notification used
     * to be called EXIT_GAME, but it is now possible to end a game without
     * quitting the app, so we've broken EXIT_GAME into END_GAME and QUIT. The
     * game controller should clean up any resources related to the game to make
     * sure they are destroyed.
     * 
     * @param n
     *            The notification object sent from the NotificationManager.
     */
    public void endGame(Notification n) {
        // reset game-related state variables. The board controller and hud
        // controller also receive this notification, and they will remove their
        // views from the screen.
        boardController_ = null;
        hudController_ = null;

        Properties config = (Properties) n.argument();
        if (config == null || !config.containsKey("hideMainMenu"))
            // show the main menu again, but this time with gameInProgress =
            // false
            toggleMainMenu(null);
    }

    /*
     * Game is currently in progress. Verify that the user wants to exit
     */
    public void attemptEndGame() {
        if (GameState.getInstance().getMode() != Mode.PLAYING_NW_GAME) {
            String[] options = { RETURN_TO_GAME, END_WITHOUT_SAVING };

            JPopUp dialogBox = new JPopUp("A game is currently in progress!");
            String ans = dialogBox.promptUser(options);

            if (ans == END_WITHOUT_SAVING) {
                GameState.getInstance().resetGameState();
                NotificationManager.getInstance().sendNotification(Notification.END_GAME);
            }
        } else {
            String[] options = { "Yes, Exit", "Back to Game" };

            JPopUp dialogBox = new JPopUp("A game is currently in progress! Are you sure?");
            String ans = dialogBox.promptUser(options);

            if (ans == "Yes, Exit") {
                GameState.getInstance().resetGameState();
                NotificationManager.getInstance().sendNotification(Notification.END_GAME);
            }

        }
    }

    public void scoreTurn(Notification n) {
        HashMap<String, Object> data = (HashMap<String, Object>) n.argument();
        Point p = (Point) data.get("location");

        TileBoardIterator iter = new TileBoardGenIterator(GameState.getInstance().getBoard(), p);

        Set<Meeple> meeple = new HashSet<Meeple>();

        // Score completed features on this tile
        RegionsCalc c = new RegionsCalc();
        for (Tile.Region r : Tile.Region.values()) {
            c.traverseRegion(iter, r);
        }
        for (Tile.Region r : Tile.Region.values()) {
            if (c.getRegionCompletion(iter.getLocation(), r)) {
                if (!meeple.containsAll(c.getMeepleList(p, r))) {
                    scoreFeature(c.getScoreOfRegion(p, r), c.getMeepleList(p, r));
                    meeple.addAll(c.getMeepleList(p, r));
                }
            }
        }

        // Score cloisters - go right then move clockwise
        // Note: do not need to recheck iter because it was checked above
        TileBoardGenIterator temp = new TileBoardGenIterator(iter);
        c.traverseRegion(temp.right(), Tile.Region.Center);
        // TODO: Change these to get the correct score (algorithm should do it)
        if (c.getRegionCompletion(temp.getLocation(), Tile.Region.Center)) {
            scoreFeature(c.getScoreOfRegion(temp.getLocation(), Tile.Region.Center),
                    c.getMeepleList(temp.getLocation(), Tile.Region.Center));
        }
        c.traverseRegion(temp.down(), Tile.Region.Center);
        if (c.getRegionCompletion(temp.getLocation(), Tile.Region.Center)) {
            scoreFeature(c.getScoreOfRegion(temp.getLocation(), Tile.Region.Center),
                    c.getMeepleList(temp.getLocation(), Tile.Region.Center));
        }
        c.traverseRegion(temp.left(), Tile.Region.Center);
        if (c.getRegionCompletion(temp.getLocation(), Tile.Region.Center)) {
            scoreFeature(c.getScoreOfRegion(temp.getLocation(), Tile.Region.Center),
                    c.getMeepleList(temp.getLocation(), Tile.Region.Center));
        }
        c.traverseRegion(temp.left(), Tile.Region.Center);
        if (c.getRegionCompletion(temp.getLocation(), Tile.Region.Center)) {
            scoreFeature(c.getScoreOfRegion(temp.getLocation(), Tile.Region.Center),
                    c.getMeepleList(temp.getLocation(), Tile.Region.Center));
        }
        c.traverseRegion(temp.up(), Tile.Region.Center);
        if (c.getRegionCompletion(temp.getLocation(), Tile.Region.Center)) {
            scoreFeature(c.getScoreOfRegion(temp.getLocation(), Tile.Region.Center),
                    c.getMeepleList(temp.getLocation(), Tile.Region.Center));
        }
        c.traverseRegion(temp.up(), Tile.Region.Center);
        if (c.getRegionCompletion(temp.getLocation(), Tile.Region.Center)) {
            scoreFeature(c.getScoreOfRegion(temp.getLocation(), Tile.Region.Center),
                    c.getMeepleList(temp.getLocation(), Tile.Region.Center));
        }
        c.traverseRegion(temp.right(), Tile.Region.Center);
        if (c.getRegionCompletion(temp.getLocation(), Tile.Region.Center)) {
            scoreFeature(c.getScoreOfRegion(temp.getLocation(), Tile.Region.Center),
                    c.getMeepleList(temp.getLocation(), Tile.Region.Center));
        }
        c.traverseRegion(temp.right(), Tile.Region.Center);
        if (c.getRegionCompletion(temp.getLocation(), Tile.Region.Center)) {
            scoreFeature(c.getScoreOfRegion(temp.getLocation(), Tile.Region.Center),
                    c.getMeepleList(temp.getLocation(), Tile.Region.Center));
        }

        if (GameState.getInstance().getDeck().tilesRemaining() == 0)
            NotificationManager.getInstance().sendNotification(Notification.GAME_OVER);
        else {
            GameState.getInstance().advanceCurrentPlayer();
            beginTurn();
        }
    }

    private void scoreFeature(Integer regionSize, List<Meeple> regionMeeple) {

        ArrayList<Player> players_ = GameState.getInstance().getPlayers();

        int counts[] = new int[players_.size()];
        int maxCount = 0;
        for (Meeple m : regionMeeple) {
            counts[m.getPlayer()] += 1;
            maxCount = Math.max(maxCount, counts[m.getPlayer()]);

            // Give meeple back
            players_.get(m.getPlayer()).shiftMeepleRemaining(1);

            // Remove the meeple from the map by deleting the "group" of
            // sprites that the meeple is attached to.
            NotificationManager.getInstance().sendNotification(Notification.MAP_REMOVE_SPRITE_GROUP, m);

            GameState.getInstance().removeMeepleFromGlobalMeepleSet(m);
            m.getParentTile().setMeeple(null);
            m.setParentTile(null);
        }

        // only score if someone claimed it
        if (maxCount > 0) {
            for (int ii = 0; ii < players_.size(); ii++) {
                if (counts[ii] == maxCount) {

                    // add to their score
                    players_.get(ii).shiftScore(regionSize);

                }
            }
        }
    }

    public void toggleMainMenu(Notification n) {
        // Determine whether the game is currently in progress

        if (menu_.isShowing()) {
            if (GameState.getInstance().getGameInProgress())
                menu_.close();

        } else {
            menu_.setGameInProgress(GameState.getInstance().getGameInProgress());

            // Make sure the instructions menu is hidden
            NotificationManager.getInstance().sendNotification(Notification.TOGGLE_INSTRUCTIONS, 0);

            DisplayHelper.getInstance().add(menu_, DisplayHelper.Layer.MODAL, DisplayHelper.Positioning.CENTER);
        }
    }

    /*
     * Notification n: optional Integer 0: hide, 1:show
     */
    public void toggleInstructions(Notification n) {
        if (instructions_.isShowing() || (n.argument() != null && ((Integer) n.argument()).equals(0))) {
            DisplayHelper.getInstance().remove(instructions_);
        } else
            DisplayHelper.getInstance().add(instructions_, DisplayHelper.Layer.MODAL,
                    DisplayHelper.Positioning.CENTER);
    }

    public void quitGame(Notification n) {
        // The board controller and HUD controller that we created during
        // game play should be deleted.

        boardController_ = null;
        hudController_ = null;

        DisplayHelper.getInstance().removeAll();
        ThreadPool.shutdown();

        Thread t = new Thread(new Runnable() {
            public void run() {
                JmDNSSingleton.getJmDNS().close();
            }
        }, "JmDNS Reaper");
        t.start();

        try {
            RemotingUtils.shutdownService(LocalHost.getName());
        } catch (RemoteException e) {
            e.printStackTrace();
        }

        // Exit
        System.exit(0);
    }

    public void loadGame(Notification n) {
        JPopUp p = new JPopUp("", "Select your saved game file...");
        File f = p.openFileDialog();

        // if the user does not select a file, cancel
        if (f == null)
            return;

        // read the file into a string
        StringBuilder contents = new StringBuilder();
        try {
            BufferedReader input = new BufferedReader(new FileReader(f));
            try {
                String line = null;
                while ((line = input.readLine()) != null) {
                    contents.append(line);
                }
            } finally {
                input.close();
            }
        } catch (Exception e) {

        }

        // close the main menu
        menu_.close();

        // unserialize the string
        XStream x = new XStream();
        x.omitField(Tile.class, "image_");
        HashMap<String, Object> map = (HashMap<String, Object>) x.fromXML(contents.toString());

        // Set gameState properties
        GameState state = GameState.getInstance();
        TileDeck deck = (TileDeck) map.get("deck");
        TileBoard board = (TileBoard) map.get("board");

        ArrayList<Player> players = (ArrayList<Player>) map.get("players");

        state.setPlayers(players);
        state.setCurrentPlayer((Integer) map.get("currentPlayer"));
        state.setTileInHand((Tile) map.get("tileInHand"));
        state.setGlobalMeepleSet((List<Meeple>) map.get("meeple"));
        state.setGameInProgress(true);
        state.setBoard(board);
        state.setDeck(deck);

        // Create a BoardController to do the heavy lifting during gameplay.
        // These two objects handle notifications from the UI (like rotate
        // tile).
        boardController_ = new BoardController();
        hudController_ = new HUDController();

        // re-attach meeple sprites on the map where they belong
        for (Meeple s : state.globalMeepleSet()) {
            MeepleSprite sprite = new MeepleSprite(s, players.get(s.getPlayer()).getMeepleColor());
            NotificationManager.getInstance().sendNotification(Notification.MAP_ADD_SPRITE, sprite);
        }

        // See if the first person is playing on this computer. If they are,
        // send the begin turn notification to activate the interface for
        // them.
        beginTurn();
    }

    public void saveGame(Notification n) {
        if (GameState.getInstance().getMode() == Mode.PLAYING_NW_GAME) {
            String[] options = { "OK" };
            JPopUp dialogBox = new JPopUp("Sorry, you cannot save a network game...");
            dialogBox.promptUser(options);
        }

        JPopUp p = new JPopUp("", "Select a location to save your game...");
        File f = p.saveFileDialog();

        // make sure we have the file extension
        if (!f.getName().endsWith(".javassonne"))
            f = new File(f.getAbsolutePath() + ".javassonne");

        // serialize shit!
        GameState state = GameState.getInstance();
        HashMap<String, Object> map = new HashMap<String, Object>();

        map.put("board", state.getBoard());
        map.put("deck", state.getDeck());
        map.put("players", state.getPlayers());
        map.put("tileInHand", state.getTileInHand());
        map.put("currentPlayer", state.getCurrentPlayerIndex());
        map.put("meeple", state.globalMeepleSet());

        XStream x = new XStream();
        x.omitField(Tile.class, "image_");

        FileWriter fw;
        try {
            fw = new FileWriter(f);
            x.toXML(map, fw);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}