/* evaluate.cpp * * Micropolis, Unix Version. This game was released for the Unix platform * in or about 1990 and has been modified for inclusion in the One Laptop * Per Child program. Copyright (C) 1989 - 2007 Electronic Arts Inc. If * you need assistance with this program, you may contact: * http://wiki.laptop.org/go/Micropolis or email micropolis@laptop.org. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. You should have received a * copy of the GNU General Public License along with this program. If * not, see . * * ADDITIONAL TERMS per GNU GPL Section 7 * * No trademark or publicity rights are granted. This license does NOT * give you any right, title or interest in the trademark SimCity or any * other Electronic Arts trademark. You may not distribute any * modification of this program using the trademark SimCity or claim any * affliation or association with Electronic Arts Inc. or its employees. * * Any propagation or conveyance of this program must include this * copyright notice and these terms. * * If you convey this program (or any modifications of it) and assume * contractual liability for the program to recipients of it, you agree * to indemnify Electronic Arts for any liability that those contractual * assumptions impose on Electronic Arts. * * You may not misrepresent the origins of this program; modified * versions of the program must be marked as such and not identified as * the original program. * * This disclaimer supplements the one included in the General Public * License. TO THE FULLEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW, THIS * PROGRAM IS PROVIDED TO YOU "AS IS," WITH ALL FAULTS, WITHOUT WARRANTY * OF ANY KIND, AND YOUR USE IS AT YOUR SOLE RISK. THE ENTIRE RISK OF * SATISFACTORY QUALITY AND PERFORMANCE RESIDES WITH YOU. ELECTRONIC ARTS * DISCLAIMS ANY AND ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES, * INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, SATISFACTORY QUALITY, * FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT OF THIRD PARTY * RIGHTS, AND WARRANTIES (IF ANY) ARISING FROM A COURSE OF DEALING, * USAGE, OR TRADE PRACTICE. ELECTRONIC ARTS DOES NOT WARRANT AGAINST * INTERFERENCE WITH YOUR ENJOYMENT OF THE PROGRAM; THAT THE PROGRAM WILL * MEET YOUR REQUIREMENTS; THAT OPERATION OF THE PROGRAM WILL BE * UNINTERRUPTED OR ERROR-FREE, OR THAT THE PROGRAM WILL BE COMPATIBLE * WITH THIRD PARTY SOFTWARE OR THAT ANY ERRORS IN THE PROGRAM WILL BE * CORRECTED. NO ORAL OR WRITTEN ADVICE PROVIDED BY ELECTRONIC ARTS OR * ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY. SOME * JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED * WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A * CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY * NOT APPLY TO YOU. */ /** * @file evaluate.cpp * @brief Evaluation and scoring for Micropolis city simulation. * * This file is part of the Micropolis game engine and handles the * evaluation and scoring aspects of a simulated city. It includes * functions to assess city value, compute population, classify city * based on population, evaluate city problems, vote on performance of * the mayor, and calculate the overall city score. The scoring system * considers various factors like crime, pollution, city class, road * effectiveness, and more to determine the success and challenges of * the city management. */ //////////////////////////////////////////////////////////////////////// #include "micropolis.h" //////////////////////////////////////////////////////////////////////// /** * @todo: These strings should not be hard coded into the core simulator. * The scripting language should look them up in translation files. */ // City Classes: // "VILLAGE", "TOWN", "CITY", "CAPITAL", "METROPOLIS", "MEGALOPOLIS" // City Levels: // "Easy", "Medium", "Hard" // City Problems: // "CRIME", "POLLUTION", "HOUSING COSTS", "TAXES", // "TRAFFIC", "UNEMPLOYMENT", "FIRES" //////////////////////////////////////////////////////////////////////// /** * Evaluate city * @todo Handle lack of voting explicitly */ void Micropolis::cityEvaluation() { //printf("cityEvaluation totalPop %d\n", totalPop); if (totalPop > 0) { short problemTable[PROBNUM]; // Score for each problem, higher the more severe the problem is. for (int z = 0; z < PROBNUM; z++) { problemTable[z] = 0; } getAssessedValue(); doPopNum(); doProblems(problemTable); getScore(problemTable); doVotes(); // How well is the mayor doing? changeEval(); } else { evalInit(); cityYes = 50; // No population => no voting. Let's say 50/50. changeEval(); } } /** * Initialize evaluation variables */ void Micropolis::evalInit() { cityYes = 0; cityPop = 0; cityPopDelta = 0; cityAssessedValue = 0; cityClass = CC_VILLAGE; cityScore = 500; cityScoreDelta = 0; for (int i = 0; i < PROBNUM; i++) { problemVotes[i] = 0; } for (int i = 0; i < CVP_PROBLEM_COMPLAINTS; i++) { problemOrder[i] = CVP_NUMPROBLEMS; } } /** * Assess value of the city. * @post #cityAssessedValue contains the total city value. * @todo Make function return the value, or change the name of the function. */ void Micropolis::getAssessedValue() { Quad z; z = roadTotal * 5; z += railTotal * 10; z += policeStationPop * 1000; z += fireStationPop * 1000; z += hospitalPop * 400; z += stadiumPop * 3000; z += seaportPop * 5000; z += airportPop * 10000; z += coalPowerPop * 3000; z += nuclearPowerPop * 6000; cityAssessedValue = z * 1000; } /** * Compute city population and city classification. * @see cityPop cityClass. */ void Micropolis::doPopNum() { Quad oldCityPop = cityPop; cityPop = getPopulation(); if (oldCityPop == -1) { oldCityPop = cityPop; } cityPopDelta = cityPop - oldCityPop; cityClass = getCityClass(cityPop); } /** Compute city population. */ Quad Micropolis::getPopulation() { Quad pop = (resPop + (comPop + indPop) * 8L) * 20L; return pop; } /** * Classify the city based on its population. * @param cityPopulation Number of people in the city. * @return City classification. * @todo Put people counts into a table. */ CityClass Micropolis::getCityClass(Quad cityPopulation) { CityClass cityClassification = CC_VILLAGE; if (cityPopulation > 2000) { cityClassification = CC_TOWN; } if (cityPopulation > 10000) { cityClassification = CC_CITY; } if (cityPopulation > 50000) { cityClassification = CC_CAPITAL; } if (cityPopulation > 100000) { cityClassification = CC_METROPOLIS; } if (cityPopulation > 500000) { cityClassification = CC_MEGALOPOLIS; } return cityClassification; } /** * Evaluate problems of the city, take votes, and decide which are the most * important ones. * @param problemTable Storage of how bad each problem is. * @post \a problemTable contains severity of each problem, * #problemVotes contains votes of each problem, * #problemOrder contains (in decreasing order) the worst problems. */ void Micropolis::doProblems(short problemTable[PROBNUM]) { bool problemTaken[PROBNUM]; // Which problems are taken? for (int z = 0; z < PROBNUM; z++) { problemTaken[z] = false; problemTable[z] = 0; } problemTable[CVP_CRIME] = crimeAverage; /* Crime */ problemTable[CVP_POLLUTION] = pollutionAverage; /* Pollution */ problemTable[CVP_HOUSING] = landValueAverage * 7 / 10; /* Housing */ problemTable[CVP_TAXES] = cityTax * 10; /* Taxes */ problemTable[CVP_TRAFFIC] = getTrafficAverage(); /* Traffic */ problemTable[CVP_UNEMPLOYMENT] = getUnemployment(); /* Unemployment */ problemTable[CVP_FIRE] = getFireSeverity(); /* Fire */ voteProblems(problemTable); for (int z = 0; z < CVP_PROBLEM_COMPLAINTS; z++) { // Find biggest problem not taken yet int maxVotes = 0; int bestProblem = CVP_NUMPROBLEMS; for (int i = 0; i < CVP_NUMPROBLEMS; i++) { if ((problemVotes[i] > maxVotes) && (!problemTaken[i])) { bestProblem = i; maxVotes = problemVotes[i]; } } // bestProblem == CVP_NUMPROBLEMS means no problem found problemOrder[z] = bestProblem; if (bestProblem < CVP_NUMPROBLEMS) { problemTaken[bestProblem] = true; } // else: No problem found. // Repeating the procedure will give the same result. // Optimize by filling all remaining entries, and breaking out } } /** * Vote on the problems of the city. * @param problemTable Storage of how bad each problem is. * * @post problemVotes contains the vote counts */ void Micropolis::voteProblems(const short problemTable[PROBNUM]) { for (int z = 0; z < PROBNUM; z++) { problemVotes[z] = 0; } int problem = 0; // Problem to vote for int voteCount = 0; // Number of votes int loopCount = 0; // Number of attempts while (voteCount < 100 && loopCount < 600) { if (getRandom(300) < problemTable[problem]) { problemVotes[problem]++; voteCount++; } problem++; if (problem > PROBNUM) { problem = 0; } loopCount++; } } /** * Compute average traffic in the city. * @return Value representing how large the traffic problem is. */ short Micropolis::getTrafficAverage() { Quad trafficTotal; short x, y, count; trafficTotal = 0; count = 1; for (x=0; x < WORLD_W; x += landValueMap.MAP_BLOCKSIZE) { for (y=0; y < WORLD_H; y += landValueMap.MAP_BLOCKSIZE) { if (landValueMap.worldGet(x, y) > 0) { trafficTotal += trafficDensityMap.worldGet(x, y); count++; } } } trafficAverage = (short)((trafficTotal / count) * 2.4); return trafficAverage; } /** * Compute severity of unemployment * @return Value representing the severity of unemployment problems */ short Micropolis::getUnemployment() { short b = (comPop + indPop) * 8; if (b == 0) { return 0; } // Ratio total people / working. At least 1. float r = ((float)resPop) / b; b = (short)((r - 1) * 255); // (r - 1) is the fraction unemployed people return min(b, (short)255); } /** * Compute severity of fire * @return Value representing the severity of fire problems */ short Micropolis::getFireSeverity() { return min(firePop * 5, 255); } /** * Compute total score * @param problemTable Storage of how bad each problem is. */ void Micropolis::getScore(const short problemTable[PROBNUM]) { int x, z; short cityScoreLast; cityScoreLast = cityScore; x = 0; for (z = 0; z < CVP_NUMPROBLEMS; z++) { x += problemTable[z]; /* add 7 probs */ } /** * @todo Should this expression depend on CVP_NUMPROBLEMS? */ x = x / 3; /* 7 + 2 average */ x = min(x, 256); z = clamp((256 - x) * 4, 0, 1000); if (resCap) { z = (int)(z * .85); } if (comCap) { z = (int)(z * .85); } if (indCap) { z = (int)(z * .85); } if (roadEffect < MAX_ROAD_EFFECT) { z -= MAX_ROAD_EFFECT - roadEffect; } if (policeEffect < MAX_POLICE_STATION_EFFECT) { // 10.0001 = 10000.1 / 1000, 1/10.0001 is about 0.1 z = (int)(z * (0.9 + (policeEffect / (10.0001 * MAX_POLICE_STATION_EFFECT)))); } if (fireEffect < MAX_FIRE_STATION_EFFECT) { // 10.0001 = 10000.1 / 1000, 1/10.0001 is about 0.1 z = (int)(z * (0.9 + (fireEffect / (10.0001 * MAX_FIRE_STATION_EFFECT)))); } if (resValve < -1000) { z = (int)(z * .85); } if (comValve < -1000) { z = (int)(z * .85); } if (indValve < -1000) { z = (int)(z * .85); } float SM = 1.0; if (cityPop == 0 || cityPopDelta == 0) { SM = 1.0; // there is nobody or no migration happened } else if (cityPopDelta == cityPop) { SM = 1.0; // city sprang into existence or doubled in size } else if (cityPopDelta > 0) { SM = ((float)cityPopDelta / cityPop) + 1.0f; } else if (cityPopDelta < 0) { SM = 0.95f + ((float)cityPopDelta / (cityPop - cityPopDelta)); } z = (int)(z * SM); z = z - getFireSeverity() - cityTax; // dec score for fires and taxes float TM = unpoweredZoneCount + poweredZoneCount; // dec score for unpowered zones if (TM > 0.0) { z = (int)(z * (float)(poweredZoneCount / TM)); } else { } z = clamp(z, 0, 1000); cityScore = (cityScore + z) / 2; cityScoreDelta = cityScore - cityScoreLast; } /** * Vote whether the mayor is doing a good job * @post #cityYes contains the number of 'yes' votes */ void Micropolis::doVotes() { int z; cityYes = 0; for (z = 0; z < 100; z++) { if (getRandom(1000) < cityScore) { cityYes++; } } } /** Push new score to the user */ void Micropolis::doScoreCard() { // The user interface should pull these raw values out and format // them. The simulator core used to format them and push them out, // but the user interface should pull them out and format them // itself. // City Evaluation ${FormatYear(currentYear())} // Public Opinion // Is the mayor doing a good job? // Yes: ${FormatPercent(cityYes)} // No: ${FormatPercent(100 - cityYes)} // What are the worst problems? // for i in range(0, CVP_PROBLEM_COMPLAINTS), // while problemOrder[i] < CVP_NUMPROBLEMS: // ${probStr[problemOrder[i]]}: // ${FormatPercent(problemVotes[problemOrder[i]])} // Statistics // Population: ${FormatNumber(cityPop)} // Net Migration: ${FormatNumber(cityPopDelta)} (last year) // Assessed Value: ${FormatMoney(cityAssessedValue)) // Category: ${cityClassStr[cityClass]} // Game Level: ${cityLevelStr[gameLevel]} callback->updateEvaluation(this, callbackVal); } /** Request that new score is displayed to the user. */ void Micropolis::changeEval() { //printf("changeEval\n"); evalChanged = true; } /** Update the score after being requested. */ void Micropolis::scoreDoer() { //printf("scoreDoer evalChanged %d\n", evalChanged); if (evalChanged) { doScoreCard(); evalChanged = false; } } /** * Return number of problem in the city. * @return Number of problems. */ int Micropolis::countProblems() { int i; for (i = 0; i < CVP_PROBLEM_COMPLAINTS; i++) { if (problemOrder[i] == CVP_NUMPROBLEMS) { break; } } return i; } /** * Return the index of the \a i-th worst problem. * @param i Number of the problem. * @return Index into the #problemOrder table of the \a i-th problem. * Returns \c -1 if such a problem does not exist. */ int Micropolis::getProblemNumber(int i) { if (i < 0 || i >= CVP_PROBLEM_COMPLAINTS || problemOrder[i] == CVP_NUMPROBLEMS) { return -1; } else { return problemOrder[i]; } } /** * Return number of votes to solve the \a i-th worst problem. * @param i Number of the problem. * @return Number of votes to solve the \a i-th worst problem. * Returns \c -1 if such a problem does not exist. */ int Micropolis::getProblemVotes(int i) { if (i < 0 || i >= CVP_PROBLEM_COMPLAINTS || problemOrder[i] == CVP_NUMPROBLEMS) { return -1; } else { return problemVotes[problemOrder[i]]; } } ////////////////////////////////////////////////////////////////////////