citylimits/core/evaluate.cpp
2024-05-03 22:50:34 -04:00

573 lines
16 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
* 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]];
}
}
////////////////////////////////////////////////////////////////////////