citylimits/core/scan.cpp

601 lines
18 KiB
C++
Raw Normal View History

2024-05-03 22:50:34 -04:00
/* scan.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 scan.cpp
* @brief Implements various scanning and analysis algorithms for
* Micropolis.
*
* This file contains functions for scanning and analyzing different
* aspects of the city in the Micropolis game, including fire and
* police station coverage, population density, pollution, land value,
* crime rates, and terrain. It also includes utility functions for
* smoothing maps and calculating distances.
*/
////////////////////////////////////////////////////////////////////////
#include "micropolis.h"
////////////////////////////////////////////////////////////////////////
/**
* Smooth a station map.
*
* Used for smoothing fire station and police station coverage maps.
* @param map Map to smooth.
*/
static void smoothStationMap(MapShort8 *map)
{
short x, y, edge;
MapShort8 tempMap(*map);
for (x = 0; x < tempMap.MAP_W; x++) {
for (y = 0; y < tempMap.MAP_H; y++) {
edge = 0;
if (x > 0) {
edge += tempMap.get(x - 1, y);
}
if (x < tempMap.MAP_W - 1) {
edge += tempMap.get(x + 1, y);
}
if (y > 0) {
edge += tempMap.get(x, y - 1);
}
if (y < tempMap.MAP_H - 1) {
edge += tempMap.get(x, y + 1);
}
edge = tempMap.get(x, y) + edge / 4;
map->set(x, y, edge / 2);
}
}
}
/**
* Make firerate map from firestation map.
* @todo Comment seems wrong; what's a firerate map?
*/
void Micropolis::fireAnalysis()
{
smoothStationMap(&fireStationMap);
smoothStationMap(&fireStationMap);
smoothStationMap(&fireStationMap);
fireStationEffectMap = fireStationMap;
newMapFlags[MAP_TYPE_FIRE_RADIUS] = 1;
newMapFlags[MAP_TYPE_DYNAMIC] = 1;
}
/** @todo The tempMap1 has MAP_BLOCKSIZE > 1, so we may be able to optimize
* the first x, y loop.
*/
void Micropolis::populationDensityScan()
{
/* sets: populationDensityMap, , , comRateMap */
tempMap1.clear();
Quad Xtot = 0;
Quad Ytot = 0;
Quad Ztot = 0;
for (int x = 0; x < WORLD_W; x++) {
for (int y = 0; y < WORLD_H; y++) {
MapValue mapValue = map[x][y];
if (mapValue & ZONEBIT) {
MapTile mapTile = mapValue & LOMASK;
int pop = getPopulationDensity(Position(x, y), mapTile) * 8;
pop = min(pop, 254);
tempMap1.worldSet(x, y, (Byte)pop);
Xtot += x;
Ytot += y;
Ztot++;
}
}
}
doSmooth1(); // tempMap1 -> tempMap2
doSmooth2(); // tempMap2 -> tempMap1
doSmooth1(); // tempMap1 -> tempMap2
assert(populationDensityMap.MAP_W == tempMap2.MAP_W);
assert(populationDensityMap.MAP_H == tempMap2.MAP_H);
// Copy tempMap2 to populationDensityMap, multiplying by 2
Byte *srcMap = tempMap2.getBase();
Byte *destMap = populationDensityMap.getBase();
for (int i = 0; i < tempMap2.MAP_W * tempMap2.MAP_H; i++) {
destMap[i] = srcMap[i] * 2;
}
computeComRateMap(); /* Compute the comRateMap */
// Compute new city center
if (Ztot > 0) { /* Find Center of Mass for City */
cityCenterX = (short)(Xtot / Ztot);
cityCenterY = (short)(Ytot / Ztot);
} else {
cityCenterX = WORLD_W / 2; /* if pop==0 center of map is city center */
cityCenterY = WORLD_H / 2;
}
// Set flags for updated maps
newMapFlags[MAP_TYPE_POPULATION_DENSITY] = 1;
newMapFlags[MAP_TYPE_RATE_OF_GROWTH] = 1;
newMapFlags[MAP_TYPE_DYNAMIC] = 1;
}
/**
* Get population of a zone.
* @param pos Position of the zone to count.
* @param tile Tile of the zone.
* @return Population of the zone.
*/
int Micropolis::getPopulationDensity(const Position &pos, MapTile tile)
{
int pop;
if (tile == FREEZ) {
pop = doFreePop(pos);
return pop;
}
if (tile < COMBASE) {
pop = getResZonePop(tile);
return pop;
}
if (tile < INDBASE) {
pop = getComZonePop(tile) * 8;
return pop;
}
if (tile < PORTBASE) {
pop = getIndZonePop(tile) * 8;
return pop;
}
return 0;
}
/* comefrom: simulate SpecialInit */
void Micropolis::pollutionTerrainLandValueScan()
{
/* Does pollution, terrain, land value */
Quad ptot, LVtot;
int x, y, z, dis;
int pollutionLevel, loc, worldX, worldY, Mx, My, pnum, LVnum, pmax;
// tempMap3 is a map of development density, smoothed into terrainMap.
tempMap3.clear();
LVtot = 0;
LVnum = 0;
for (x = 0; x < landValueMap.MAP_W; x++) {
for (y = 0; y < landValueMap.MAP_H; y++) {
pollutionLevel = 0;
bool landValueFlag = false;
worldX = x * 2;
worldY = y * 2;
for (Mx = worldX; Mx <= worldX + 1; Mx++) {
for (My = worldY; My <= worldY + 1; My++) {
loc = (map[Mx][My] & LOMASK);
if (loc) {
if (loc < RUBBLE) {
// Increment terrain memory.
Byte value = tempMap3.get(x >>1, y >>1);
tempMap3.set(x >>1, y >>1, value + 15);
continue;
}
pollutionLevel += getPollutionValue(loc);
if (loc >= ROADBASE) {
landValueFlag = true;
}
}
}
}
/* XXX ??? This might have to do with the radiation tile returning -40.
if (pollutionLevel < 0) {
pollutionLevel = 250;
}
*/
pollutionLevel = min(pollutionLevel, 255);
tempMap1.set(x, y, pollutionLevel);
if (landValueFlag) { /* LandValue Equation */
dis = 34 - getCityCenterDistance(worldX, worldY) / 2;
dis = dis <<2;
dis += terrainDensityMap.get(x >>1, y >>1);
dis -= pollutionDensityMap.get(x, y);
if (crimeRateMap.get(x, y) > 190) {
dis -= 20;
}
dis = clamp(dis, 1, 250);
landValueMap.set(x, y, dis);
LVtot += dis;
LVnum++;
} else {
landValueMap.set(x, y, 0);
}
}
}
if (LVnum > 0) {
landValueAverage = (short)(LVtot / LVnum);
} else {
landValueAverage = 0;
}
doSmooth1(); // tempMap1 -> tempMap2
doSmooth2(); // tempMap2 -> tempMap1
pmax = 0;
pnum = 0;
ptot = 0;
for (x = 0; x < WORLD_W; x += pollutionDensityMap.MAP_BLOCKSIZE) {
for (y = 0; y < WORLD_H; y += pollutionDensityMap.MAP_BLOCKSIZE) {
z = tempMap1.worldGet(x, y);
pollutionDensityMap.worldSet(x, y, z);
if (z) { /* get pollute average */
pnum++;
ptot += z;
/* find max pol for monster */
if (z > pmax || (z == pmax && (getRandom16() & 3) == 0)) {
pmax = z;
pollutionMaxX = x;
pollutionMaxY = y;
}
}
}
}
if (pnum) {
pollutionAverage = (short)(ptot / pnum);
} else {
pollutionAverage = 0;
}
smoothTerrain();
newMapFlags[MAP_TYPE_POLLUTION] = 1;
newMapFlags[MAP_TYPE_LAND_VALUE] = 1;
newMapFlags[MAP_TYPE_DYNAMIC] = 1;
}
/**
* Return pollution of a tile value
* @param loc Tile character
* @return Value of the pollution (0..255, bigger is worse)
*/
int Micropolis::getPollutionValue(int loc)
{
if (loc < POWERBASE) {
if (loc >= HTRFBASE) {
return /* 25 */ 75; /* heavy traf */
}
if (loc >= LTRFBASE) {
return /* 10 */ 50; /* light traf */
}
if (loc < ROADBASE) {
if (loc > FIREBASE) {
return /* 60 */ 90;
}
/* XXX: Why negative pollution from radiation? */
if (loc >= RADTILE) {
return /* -40 */ 255; /* radioactivity */
}
}
return 0;
}
if (loc <= LASTIND) {
return 0;
}
if (loc < PORTBASE) {
return 50; /* Ind */
}
if (loc <= LASTPOWERPLANT) {
return /* 60 */ 100; /* prt, aprt, cpp */
}
return 0;
}
/**
* Compute Manhattan distance between given world position and center of the
* city.
* @param x X world coordinate of given position.
* @param y Y world coordinate of given position.
* @return Manhattan distance (\c dx+dy ) between both positions.
* @note For long distances (> 64), value 64 is returned.
*/
int Micropolis::getCityCenterDistance(int x, int y)
{
int xDis, yDis;
if (x > cityCenterX) {
xDis = x - cityCenterX;
} else {
xDis = cityCenterX - x;
}
if (y > cityCenterY) {
yDis = y - cityCenterY;
} else {
yDis = cityCenterY - y;
}
return min(xDis + yDis, 64);
}
/** Smooth police station map and compute crime rate */
void Micropolis::crimeScan()
{
smoothStationMap(&policeStationMap);
smoothStationMap(&policeStationMap);
smoothStationMap(&policeStationMap);
Quad totz = 0;
int numz = 0;
int cmax = 0;
for (int x = 0; x < WORLD_W; x += crimeRateMap.MAP_BLOCKSIZE) {
for (int y = 0; y < WORLD_H; y += crimeRateMap.MAP_BLOCKSIZE) {
int z = landValueMap.worldGet(x, y);
if (z > 0) {
++numz;
z = 128 - z;
z += populationDensityMap.worldGet(x, y);
z = min(z, 300);
z -= policeStationMap.worldGet(x, y);
z = clamp(z, 0, 250);
crimeRateMap.worldSet(x, y, (Byte)z);
totz += z;
// Update new crime hot-spot
if (z > cmax || (z == cmax && (getRandom16() & 3) == 0)) {
cmax = z;
crimeMaxX = x;
crimeMaxY = y;
}
} else {
crimeRateMap.worldSet(x, y, 0);
}
}
}
if (numz > 0) {
crimeAverage = (short)(totz / numz);
} else {
crimeAverage = 0;
}
policeStationEffectMap = policeStationMap;
newMapFlags[MAP_TYPE_CRIME] = 1;
newMapFlags[MAP_TYPE_POLICE_RADIUS] = 1;
newMapFlags[MAP_TYPE_DYNAMIC] = 1;
}
/* comefrom: pollutionTerrainLandValueScan */
void Micropolis::smoothTerrain()
{
if (donDither & 1) {
int x, y = 0, dir = 1;
unsigned z = 0;
for (x = 0; x < terrainDensityMap.MAP_W; x++) {
for (; y != terrainDensityMap.MAP_H && y != -1; y += dir) {
z +=
tempMap3.get((x == 0) ? x : (x - 1), y) +
tempMap3.get((x == (terrainDensityMap.MAP_W - 1)) ? x : (x + 1), y) +
tempMap3.get(x, (y == 0) ? (0) : (y - 1)) +
tempMap3.get(x, (y == (terrainDensityMap.MAP_H - 1)) ? y : (y + 1)) +
(tempMap3.get(x, y) <<2);
Byte val = (Byte)(z / 8);
terrainDensityMap.set(x, y, val);
z &= 0x7;
}
dir = -dir;
y += dir;
}
} else {
short x, y;
for (x = 0; x < terrainDensityMap.MAP_W; x++) {
for (y = 0; y < terrainDensityMap.MAP_H; y++) {
unsigned z = 0;
if (x > 0) {
z += tempMap3.get(x - 1, y);
}
if (x < (terrainDensityMap.MAP_W - 1)) {
z += tempMap3.get(x + 1, y);
}
if (y > 0) {
z += tempMap3.get(x, y - 1);
}
if (y < (terrainDensityMap.MAP_H - 1)) {
z += tempMap3.get(x, y + 1);
}
Byte val = (Byte)(z / 4 + tempMap3.get(x, y)) / 2;
terrainDensityMap.set(x, y, val);
}
}
}
}
/**
* Perform smoothing with or without dithering.
* @param srcMap Source map.
* @param destMap Destination map.
* @param ditherFlag Function should apply dithering.
*/
static void smoothDitherMap(const MapByte2 &srcMap,
MapByte2 *destMap,
bool ditherFlag)
{
if (ditherFlag) {
int x, y = 0, z = 0, dir = 1;
for (x = 0; x < srcMap.MAP_W; x++) {
for (; y != srcMap.MAP_H && y != -1; y += dir) {
z +=
srcMap.get((x == 0) ? x : (x - 1), y) +
srcMap.get((x == srcMap.MAP_W - 1) ? x : (x + 1), y) +
srcMap.get(x, (y == 0) ? (0) : (y - 1)) +
srcMap.get(x, (y == (srcMap.MAP_H - 1)) ? y : (y + 1)) +
srcMap.get(x, y);
Byte val = (Byte)(z / 4);
destMap->set(x, y, val);
z &= 3;
}
dir = -dir;
y += dir;
}
} else {
short x, y, z;
for (x = 0; x < srcMap.MAP_W; x++) {
for (y = 0; y < srcMap.MAP_H; y++) {
z = 0;
if (x > 0) {
z += srcMap.get(x - 1, y);
}
if (x < srcMap.MAP_W - 1) {
z += srcMap.get(x + 1, y);
}
if (y > 0) {
z += srcMap.get(x, y - 1);
}
if (y < (srcMap.MAP_H - 1)) {
z += srcMap.get(x, y + 1);
}
z = (z + srcMap.get(x, y)) >>2;
if (z > 255) {
z = 255;
}
destMap->set(x, y, (Byte)z);
}
}
}
}
/* Smooth Micropolis::tempMap1 to Micropolis::tempMap2 */
void Micropolis::doSmooth1()
{
smoothDitherMap(tempMap1, &tempMap2, donDither & 2);
}
/* Smooth Micropolis::tempMap2 to Micropolis::tempMap1 */
void Micropolis::doSmooth2()
{
smoothDitherMap(tempMap2, &tempMap1, donDither & 4);
}
/**
* Compute distance to city center for the entire map.
* @see comRateMap
*/
void Micropolis::computeComRateMap()
{
short x, y, z;
for (x = 0; x < comRateMap.MAP_W; x++) {
for (y = 0; y < comRateMap.MAP_H; y++) {
z = (short)(getCityCenterDistance(x * 8,y * 8) / 2); // 0..32
z = z * 4; // 0..128
z = 64 - z; // 64..-64
comRateMap.set(x, y, z);
}
}
}
////////////////////////////////////////////////////////////////////////