SatisfactionHandler.java

package com.vikingz.unitycoon.util;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.vikingz.unitycoon.building.Building;
import com.vikingz.unitycoon.building.BuildingStats.BuildingType;
import com.vikingz.unitycoon.global.GameGlobals;

/**
 * This new class manages the student satisfaction calculation.
 * It was implemented to complete UR_SATISFACTION.
 */
public class SatisfactionHandler {

    static int satisfaction;
    static int satisfactionModifier;

    public SatisfactionHandler() {
        satisfaction = 10; //Starting satisfaction is 10
        satisfactionModifier = 0;
    }

    public int getSatisfaction() {
        //Insures satisfaction is no more than 100 and no less than 0
        if(satisfaction < 0) {
            return 0;
        }
        else if (satisfaction > 100){
            return 100;
        }
        return satisfaction;
    }

    /**
     * Calculates satisfaction
     * @param placedBuildings List of placed buildings on the map
     * @return Int Amount of satisfaction gained
     */
    public void recalculateSatisfaction(List<Building> placedBuildings){
        satisfaction = calculateMaxSatisfaction();
        satisfaction -= calculateBuildingProportionLoss();
        satisfaction -= calculateProximityLoss(placedBuildings);

        satisfaction += satisfactionModifier;
    }

    /**
     * Calculates a loss to add to satisfaction if the building types are not near enough too each other.
     * @return Loss to add to satisfaction
     */
    private int calculateProximityLoss(List<Building> placedBuildings) {
        int proximityLoss = 0;

        for (Building currentBuilding : placedBuildings) {
            boolean nearAcademic = false;
            boolean nearFood = false;
            boolean nearRecreation = false;
            boolean nearAccomodation = false;

            // Used to set what type of building every other building should be affected by
            Map<BuildingType,BuildingType[]> caresAbout = new HashMap<BuildingType, BuildingType[]>();
            caresAbout.put(BuildingType.ACCOMODATION, new BuildingType[] { BuildingType.ACADEMIC, BuildingType.FOOD, BuildingType.RECREATIONAL });
            caresAbout.put(BuildingType.ACADEMIC, new BuildingType[] { BuildingType.FOOD, BuildingType.ACCOMODATION });
            caresAbout.put(BuildingType.FOOD, new BuildingType[] { BuildingType.ACCOMODATION, BuildingType.ACADEMIC });
            caresAbout.put(BuildingType.RECREATIONAL, new BuildingType[] { BuildingType.ACCOMODATION });

            if (!currentBuilding.getConstructing()) {
                for (Building otherBuilding : placedBuildings) {
                    if (!otherBuilding.getConstructing()) {
                        // Checks if the other type of building is within 4 buildings distance
                        if (otherBuilding.getBuildingType() == BuildingType.ACADEMIC && !nearAcademic) {
                            if (getDistance(currentBuilding, otherBuilding) < 4 * GameGlobals.SCREEN_BUILDING_SIZE) {
                                nearAcademic = true;
                            }
                        }
                        else if (otherBuilding.getBuildingType() == BuildingType.FOOD && !nearFood) {
                            if (getDistance(currentBuilding, otherBuilding) < 4 * GameGlobals.SCREEN_BUILDING_SIZE) {
                                nearFood = true;
                            }
                        }
                        else if (otherBuilding.getBuildingType() == BuildingType.RECREATIONAL && !nearRecreation) {
                            if (getDistance(currentBuilding, otherBuilding) < 4 * GameGlobals.SCREEN_BUILDING_SIZE) {
                                nearRecreation = true;
                            }
                        }
                        else if (otherBuilding.getBuildingType() == BuildingType.ACCOMODATION && !nearAccomodation) {
                            if (getDistance(currentBuilding, otherBuilding) < 4 * GameGlobals.SCREEN_BUILDING_SIZE) {
                                nearAccomodation = true;
                            }
                        }
                    }
                }

                // Adds 1 to proximity for each building that isn't close enough
                if(!nearAcademic && Arrays.asList(caresAbout.get(currentBuilding.getBuildingType())).contains(BuildingType.ACADEMIC) ) {proximityLoss++;}
                if(!nearFood && Arrays.asList(caresAbout.get(currentBuilding.getBuildingType())).contains(BuildingType.FOOD)) {proximityLoss++;}
                if(!nearRecreation && Arrays.asList(caresAbout.get(currentBuilding.getBuildingType())).contains(BuildingType.RECREATIONAL)) {proximityLoss++;}
                if(!nearAccomodation && Arrays.asList(caresAbout.get(currentBuilding.getBuildingType())).contains(BuildingType.ACCOMODATION)) {proximityLoss++;}
            }
        }

        return proximityLoss;
    }

    /**
     * Gets the distance between any 2 buildings defined by the taxicab metric:
     * Distance = |xcoord1 - xcoord2| + |ycoord1 - ycoord2|
     * @param accBuilding accomodation building
     * @param otherBuilding another building
     * @return the distance value
     */
    private int getDistance(Building accBuilding, Building otherBuilding) {
        int distance = (int) (Math.abs(accBuilding.getX() - otherBuilding.getX())
            + Math.abs(Math.abs(accBuilding.getY() - otherBuilding.getY())));

        return distance;
    }

    /**
     * Calculates a loss to add to satisfaction if the number of non-accomodation buildings is much 
     * higher than the number of accomodation buildings.
     * @return Loss to add to satisfaction
     */
    private int calculateBuildingProportionLoss() {
        int buildingProportionLoss = 0;

        // Checks if recreational buildings is in proportion with accomodation buildings.
        if (GameGlobals.RECREATIONAL_BUILDINGS_COUNT >= 4 // There should be no more than 1 per every 6 and never more than 4.
                || (GameGlobals.RECREATIONAL_BUILDINGS_COUNT > Math.ceil((GameGlobals.ACCOMODATION_BUILDINGS_COUNT + 1) / 6.0))) {
            buildingProportionLoss += 10;
        }
        // Checks if food buildings is in proportion with accomodation buildings.
        if (GameGlobals.FOOD_BUILDINGS_COUNT >= 6 // There should be no more than 1 per every 4 and never more than 6.
                || (GameGlobals.FOOD_BUILDINGS_COUNT > Math.ceil((GameGlobals.ACCOMODATION_BUILDINGS_COUNT + 1)  / 4.0))) {
            buildingProportionLoss += 10;
        }
        // Checks if the recreational buildings is in proportion with accomodation buildings.
        if (GameGlobals.ACADEMIC_BUILDINGS_COUNT >= 12 // There should be no more than 1 per every 2 and never more than 12.
                || (GameGlobals.ACADEMIC_BUILDINGS_COUNT > Math.ceil((GameGlobals.ACCOMODATION_BUILDINGS_COUNT + 1) / 2.0))){
            buildingProportionLoss += 10;
        }

        return buildingProportionLoss;
    }

    /**
     * Increases the satisfaction by a given amount. Stores that amount so that it is added everytime 
     * satisfaction is calculated.
     * @param bonus
     */
    public void addBonus(int bonus) {
        satisfactionModifier += bonus;
        satisfaction += bonus;
    }

    /**
     * Decreases the satisfaction by a given amount. Stores that amount so that it is removed everytime 
     * satisfaction is calculated.
     * @param penalty
     */
    public void applyPenalty(int penalty) {
        satisfactionModifier -= penalty;
        satisfaction -= penalty;
    }

    /**
     * Calculates the maximum satisfaction the user can have based on the number of placed buildings.
     * @return the max satisfaction
     */
    private int calculateMaxSatisfaction() {
        int maxSatisfaction = 0;
        int numBuildings = GameGlobals.ACADEMIC_BUILDINGS_COUNT + GameGlobals.ACCOMODATION_BUILDINGS_COUNT
            + GameGlobals.FOOD_BUILDINGS_COUNT + GameGlobals.RECREATIONAL_BUILDINGS_COUNT;

        if (numBuildings <= 24) {
            maxSatisfaction = Math.round((10f / 3f) * numBuildings) + 10;
        }
        else if (numBuildings <= 36) {
            maxSatisfaction = 90;
        }
        else {
            maxSatisfaction = 90 - Math.round((10f / 3f) * (numBuildings - 36));
        }

        return maxSatisfaction;
    }
}