/* * Copyright (c) 2012 Felix Mo. All rights reserved. * * CitySim is published under the terms of the MIT License. See the LICENSE file for more information. * */ import greenfoot.*; // (World, Actor, GreenfootImage, Greenfoot and MouseInfo) import java.util.HashMap; import java.util.ArrayList; import java.io.File; import java.awt.Point; import java.util.Arrays; import java.lang.Thread; /** * 'City' acts as a container for all objects / "Actors" in the game (incl. map, HUD, etc.) * * @author Felix Mo * @version v1.0 * @since 2012-02-11 */ public class City extends World { // --------------------------------------------------------------------------------------------------------------------- /* * CONSTANTS * */ private final int FREQ_WRITE = 10; // # of secs before updated data is written to DB // Initial values private final int INITIAL_CASH = 100000; // amount of $ to start with private final int INITIAL_POP = 0; // # of people in the population to start with private final int INITIAL_DATE_DAYS = 1; // number of days elasped in the inital date private final int INITIAL_DATE_MONTHS = 1; // number of months elasped in the inital date private final int INITIAL_DATE_YEARS = 0; // number of years elapsed in the inital date private final int INITIAL_TAX_RATE = 10; private final int INITIAL_SCORE = 100; // --------------------------------------------------------------------------------------------------------------------- /* * STATIC VARIABLES * */ private static City instance; // pointer to an instance of 'City'; to be used globally to access 'City' private static final Overlay overlay = new Overlay(); // --------------------------------------------------------------------------------------------------------------------- /* * REFERENCES * */ private MenuBar menuBar; // Menu bar containing game controls private Hint hint; // Active hint // --------------------------------------------------------------------------------------------------------------------- /* * INSTANCE VARIABLES * */ private int writeCountdown = 0; // Countdown until updated data is written to DB private int lastZoneID = 0; // Keeps track of the last ID assigned to a zone; this value is incremented and assigned to new zones private int score; // --------------------------------------------------------------------------------------------------------------------- /* * CONSTRUCTORS * */ /** * Constructs a City. */ public City(String name, String path) { super(1024, 768, 1, false); // Create a 1024 x 768 'World' with a cell size of 1px that does not restrict 'actors' to the world boundary // Mark a new session in the logs CSLogger.sharedLogger().info("***** NEW SESSION *****"); // Set Greenfoot paint order to ensure that Actors are layered properly setPaintOrder(QueryModalWindow.class, Overlay.class, Hint.class, MenuItem.class, Menu.class, MenuBarItem.class, MenuBar.class, Label.class, MinimapViewport.class, Minimap.class, HUD.class, Selection.class, AnimationLayer.class, Map.class); if (name != null) { Data.setNameAndPath(name, path); } else { Data.setPath(path); } // If the data source has just created a new DB (b/c it did not exist), seed it with initial stats. and metadata if (name != null) { // - Metadata - HashMap mapMetadata = new HashMap(1); // Initial metadata mapMetadata.put(Data.METADATA_NAME, name); // FOR TESTING PURPOSES Data.insertMapMetadata(mapMetadata); // - City stats - HashMap cityStats = new HashMap(6); // Initial city stats. cityStats.put(Data.CITYSTATS_DAYS, INITIAL_DATE_DAYS); cityStats.put(Data.CITYSTATS_MONTHS, INITIAL_DATE_MONTHS); cityStats.put(Data.CITYSTATS_YEARS, INITIAL_DATE_YEARS); cityStats.put(Data.CITYSTATS_POPULATION, INITIAL_POP); cityStats.put(Data.CITYSTATS_CASH, INITIAL_CASH); cityStats.put(Data.CITYSTATS_TAXRATE, INITIAL_TAX_RATE); cityStats.put(Data.CITYSTATS_LAST_TAX_COLLECTION, 0); cityStats.put(Data.CITYSTATS_SCORE, INITIAL_SCORE); Data.insertCityStats(cityStats); // - Zone stats - HashMap zoneStats = new HashMap(4); zoneStats.put(Data.ZONESTATS_RESIDENTIALCOUNT, 0); zoneStats.put(Data.ZONESTATS_INDUSTRIALCOUNT, 0); zoneStats.put(Data.ZONESTATS_COMMERCIALCOUNT, 0); zoneStats.put(Data.ZONESTATS_LASTZONEID, -1); Data.insertZoneStats(zoneStats); // - Road stats - HashMap roadStats = new HashMap(1); roadStats.put(Data.ROADSTATS_STREETCOUNT, 0); Data.insertRoadStats(roadStats); } // Resume tracking date from last saved date HashMap cityStats = Data.cityStats(); this.score = ((Integer)cityStats.get(Data.CITYSTATS_SCORE)).intValue(); Date.set((Integer)cityStats.get(Data.CITYSTATS_DAYS), (Integer)cityStats.get(Data.CITYSTATS_MONTHS), (Integer)cityStats.get(Data.CITYSTATS_YEARS)); Population.initialSet((Integer)cityStats.get(Data.CITYSTATS_POPULATION)); // Initalize the cash store from the last known value in the DB Cash.set(((Integer)cityStats.get(Data.CITYSTATS_CASH))); Taxation.setLastCollection(((Integer)cityStats.get(Data.CITYSTATS_LAST_TAX_COLLECTION))); Taxation.setRate(((Integer)cityStats.get(Data.CITYSTATS_TAXRATE))); // Create and add a new map for the city addObject(new Map(), 512, 333); // Create and add HUD addObject(new HUD(), 512, 653); // Create and add the representation of the viewport into the minimap addObject(new MinimapViewport(new Point(0, 0)), 112, 658); // Create and add the menubar menuBar = new MenuBar(); addObject(menuBar, 512, 14); // - Menu bar items - ArrayList<String> menuBarItems = new ArrayList(7); menuBarItems.add(Zone.NAME); menuBarItems.add(Road.NAME); menuBarItems.add(PowerGrid.NAME); menuBarItems.add(ProtectionZone.NAME); menuBarItems.add(Recreation.NAME); menuBarItems.add(Tool.NAME); menuBar.setItems(menuBarItems); // * Menu items * /* * NOTE * * Menu items need to be declared in 'MenuItemEvent' as well and implemented in 'MenuItemEventListener'. */ // -> Zoning (first) ArrayList<String> zoneItems = new ArrayList(3); zoneItems.add(ResidentialZone.NAME); zoneItems.add(CommercialZone.NAME); zoneItems.add(IndustrialZone.NAME); menuBar.setMenuItemsForItem(Zone.NAME, zoneItems); // -> Transportation ArrayList<String> roadItems = new ArrayList(1); roadItems.add(Street.NAME); menuBar.setMenuItemsForItem(Road.NAME, roadItems); // -> Power ArrayList<String> powerItems = new ArrayList(3); powerItems.add(PowerLine.NAME); powerItems.add(CoalPowerPlant.NAME); powerItems.add(NuclearPowerPlant.NAME); menuBar.setMenuItemsForItem(PowerGrid.NAME, powerItems); // -> Protection ArrayList<String> protectionItems = new ArrayList(2); protectionItems.add(FireStation.NAME); protectionItems.add(PoliceStation.NAME); menuBar.setMenuItemsForItem(ProtectionZone.NAME, protectionItems); // Recreation ArrayList<String> recreationItems = new ArrayList(2); recreationItems.add(Park.NAME); recreationItems.add(Stadium.NAME); menuBar.setMenuItemsForItem(Recreation.NAME, recreationItems); // -> Tools ArrayList<String> toolItems = new ArrayList(2); toolItems.add(Bulldozer.NAME); toolItems.add(Query.NAME); menuBar.setMenuItemsForItem(Tool.NAME, toolItems); // * END of menu items * instance = this; // Start tracking time Date.start(); // Start playing background music SoundManager.playBackgroundMusic(); } // --------------------------------------------------------------------------------------------------------------------- /* * GREENFOOT EVENTS * */ /** * Overrides started() in {@link World} from the Greenfoot framework. This is called when the game is started. */ public void started() { CSLogger.sharedLogger().info("Game has started..."); if (!Data.connectionIsOpen()) { Data.resumeConnection(); } Date.start(); SoundManager.playBackgroundMusic(); } /** * Overrides stopped() in {@link World} from the Greenfoot framework. This is called when the game is stopped. */ public void stopped() { CSLogger.sharedLogger().info("Game has stopped."); Date.stop(); Data.closeConnection(); SoundManager.pauseBackgroundMusic(); } // --------------------------------------------------------------------------------------------------------------------- /* * DATE * */ /** * <p> * This method is called when date is incremented (every 1 sec). * </p> * * <p> * This method tells {@link Data} to write the latest statistics to the database and hints to the JVM to perform GC <b>every 10 secs</b>, and refreshes the HUD values <b>every 1 sec</b>. * </p> */ public void didIncrementDate() { // Write to DB writeCountdown++; if (writeCountdown % 7 == 0) { if (CSThread.count() == 0) { new CitySimulationThread().start(); } } if (writeCountdown == FREQ_WRITE) { Data.updateCityStats(currentCityStats()); writeCountdown = 0; // Run Java garbage collector to cleanup System.gc(); } // Refresh values for HUD every 1 sec HUD.getInstance().refresh(valuesForHUD()); // Refresh minimap if there are changes to map if (Minimap.getInstance().shouldUpdate()) { new MinimapDrawThread().start(); Minimap.getInstance().setShouldUpdate(false); } if (writeCountdown % 3 == 0) { if (PowerGrid.shouldEvaluate() && !Map.getInstance().selection().selectionMode()) { new PowerGridEvaluationThread().start(); } AnimationLayer.getInstance().setZones(Data.zonesMatchingCriteria("powered = -1")); } } // --------------------------------------------------------------------------------------------------------------------- /* * MAP * */ /** * <p> * This method is called when the minimap viewport has been moved. * </p> * (i.e. minimap has been clicked on) */ public void didMoveViewportTo(int x, int y) { // Move the map Map.getInstance().viewportDidMoveTo(x, y); } /** * <p> * This method is called when the map is moved. * </p> * (i.e. when the user scrolls the map). * <p> * * </p> * <p> * Updates the viewport position on the minimap after the map has moved. * </p> */ public void didMoveMapTo(int x, int y) { // Move the representation of the viewport in the minimap MinimapViewport.getInstance().didMoveViewportToCell(x, y); } // --------------------------------------------------------------------------------------------------------------------- /* * MODAL VIEWS / DIALOGS * */ /** * Removes the active hint from view. */ public void removeHint() { removeObject(this.hint); } // --------------------------------------------------------------------------------------------------------------------- /* * OVERLAY * */ /** * Shows an overlay to fade out the screen. */ public void showOverlay() { addObject(City.overlay, 512, 384); } /** * Hides the overlay. */ public void hideOverlay() { removeObject(City.overlay); } // --------------------------------------------------------------------------------------------------------------------- /* * HELPERS * */ // Returns formatted values for the HUD labels private HashMap valuesForHUD() { HashMap values = new HashMap(6); values.put(HUD.NAME, Population.category() + " of " + Data.mapMetadata().get(Data.METADATA_NAME)); values.put(HUD.POPULATION, Population.asString()); values.put(HUD.DATE, Date.asString()); values.put(HUD.CASH, Cash.asString()); values.put(HUD.TAXRATE, Taxation.rateString()); values.put(HUD.SCORE, this.score); return values; } // Returns the city stats. w/o formatting (i.e. for writing the values to the DB) private HashMap currentCityStats() { HashMap stats = new HashMap(8); stats.put(Data.CITYSTATS_DAYS, Date.days()); stats.put(Data.CITYSTATS_MONTHS, Date.months()); stats.put(Data.CITYSTATS_YEARS, Date.years()); stats.put(Data.CITYSTATS_POPULATION, Population.size()); stats.put(Data.CITYSTATS_CASH, Cash.value()); stats.put(Data.CITYSTATS_TAXRATE, Taxation.rate()); stats.put(Data.CITYSTATS_LAST_TAX_COLLECTION, Taxation.lastCollection()); stats.put(Data.CITYSTATS_SCORE, this.score); return stats; } // --------------------------------------------------------------------------------------------------------------------- /* * ACCESSORS * */ /** * Returns the active {@link Hint}. * * @return The active {@link Hint}. */ public Hint hint() { return this.hint; } /** * Sets the active hint. This reference to it will be used later to remove it from view. * * @param hint The hint to be made active. */ public void setHint(Hint hint) { this.hint = hint; addObject(this.hint, Hint.ORIGIN_X, Hint.ORIGIN_Y); } /** * Returns an instance of {@link City}. * * @return An instance of City. */ public static City getInstance() { return instance; } /** * Returns an instance of {@link MenuBar}. * * @return An instance of MenuBar. */ public MenuBar menuBar() { return this.menuBar; } /** * Returns the city's current score. * * @return The city's current score (0 - 100%). */ public int score() { return this.score; } /** * Sets the city's score. The score must be between 0 - 100. * * @param An integer with the new score. */ public void setScore(int value) { CSLogger.sharedLogger().info("Setting city score to " + value); // Bound score to limits this.score = Math.min(100, Math.max(0, value)); if (value <= 0) { // Impeachment; game over. // Show message imforming user of impeachment showOverlay(); new MessageDialog("You have been impeached as mayor! Game over! Press \"reset\" to play again."); Greenfoot.stop(); } } }