/* generate.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 generate.cpp * @brief Contains functions related to terrain generation in * Micropolis. * * This file is a part of Micropolis and is responsible for generating * various terrain features such as rivers, lakes, islands, and * forests. It includes functions for creating randomized maps and * modifying terrain features. The terrain generation methods take * into account different parameters like seed value for random * generation and terrain characteristics to create diverse and unique * city landscapes. */ //////////////////////////////////////////////////////////////////////// #include "micropolis.h" //////////////////////////////////////////////////////////////////////// /** * Create a new map for a city. * @bug We use a random number generator to draw a seed for initializing the * random number generator? */ void Micropolis::generateSomeRandomCity() { generateSomeCity(getRandom16()); } /** * Generate a map for a city. * @param seed Random number generator initializing seed */ void Micropolis::generateSomeCity(int seed) { cityFileName = ""; generateMap(seed); scenario = SC_NONE; cityTime = 0; initSimLoad = 2; doInitialEval = false; initWillStuff(); resetMapState(); resetEditorState(); invalidateMaps(); updateFunds(); doSimInit(); simUpdate(); callback->didGenerateMap(this, callbackVal, seed); } /** * Generate a map. * @param seed Initialization seed for the random generator. */ void Micropolis::generateMap(int seed) { generatedCitySeed = seed; seedRandom(seed); // Construct land. if (terrainCreateIsland < 0) { if (getRandom(100) < 10) { /* chance that island is generated */ makeIsland(); return; } } if (terrainCreateIsland == 1) { makeNakedIsland(); } else { clearMap(); } // Lay a river. if (terrainCurveLevel != 0) { int terrainXStart = 40 + getRandom(WORLD_W - 80); int terrainYStart = 33 + getRandom(WORLD_H - 67); Position terrainPos(terrainXStart, terrainYStart); doRivers(terrainPos); } // Lay a few lakes. if (terrainLakeLevel != 0) { makeLakes(); } smoothRiver(); // And add trees. if (terrainTreeLevel != 0) { doTrees(); } } /** Clear the whole world to ::DIRT tiles */ void Micropolis::clearMap() { short x, y; for (x = 0; x < WORLD_W; x++) { for (y = 0; y < WORLD_H; y++) { map[x][y] = DIRT; } } } /** Clear everything from all land */ void Micropolis::clearUnnatural() { short x, y; for (x = 0; x < WORLD_W; x++) { for (y = 0; y < WORLD_H; y++) { if (map[x][y] > WOODS) { map[x][y] = DIRT; } } } } /** * Construct a plain island as world, surrounded by 5 tiles of river. */ void Micropolis::makeNakedIsland() { const int terrainIslandRadius = ISLAND_RADIUS; int x, y; for (x = 0; x < WORLD_W; x++) { for (y = 0; y < WORLD_H; y++) { if ((x < 5) || (x >= WORLD_W - 5) || (y < 5) || (y >= WORLD_H - 5)) { map[x][y] = RIVER; } else { map[x][y] = DIRT; } } } for (x = 0; x < WORLD_W - 5; x += 2) { int mapY = getERandom(terrainIslandRadius); plopBRiver(Position(x, mapY)); mapY = (WORLD_H - 10) - getERandom(terrainIslandRadius); plopBRiver(Position(x, mapY)); plopSRiver(Position(x, 0)); plopSRiver(Position(x, WORLD_H - 6)); } for (y = 0; y < WORLD_H - 5; y += 2) { int mapX = getERandom(terrainIslandRadius); plopBRiver(Position(mapX, y)); mapX = (WORLD_W - 10) - getERandom(terrainIslandRadius); plopBRiver(Position(mapX, y)); plopSRiver(Position(0, y)); plopSRiver(Position(WORLD_W - 6, y)); } } /** Construct a new world as an island */ void Micropolis::makeIsland() { makeNakedIsland(); smoothRiver(); doTrees(); } /** * Make a number of lakes, depending on the Micropolis::terrainLakeLevel. */ void Micropolis::makeLakes() { short numLakes; if (terrainLakeLevel < 0) { numLakes = getRandom(10); } else { numLakes = terrainLakeLevel / 2; } while (numLakes > 0) { int x = getRandom(WORLD_W - 21) + 10; int y = getRandom(WORLD_H - 20) + 10; makeSingleLake(Position(x, y)); numLakes--; } } /** * Make a random lake at \a pos. * @param pos Rough position of the lake. */ void Micropolis::makeSingleLake(const Position &pos) { int numPlops = getRandom(12) + 2; while (numPlops > 0) { Position plopPos(pos, getRandom(12) - 6, getRandom(12) - 6); if (getRandom(4)) { plopSRiver(plopPos); } else { plopBRiver(plopPos); } numPlops--; } } /** * Splash a bunch of trees down near (\a xloc, \a yloc). * * Amount of trees is controlled by Micropolis::terrainTreeLevel. * @param xloc Horizontal position of starting point for splashing trees. * @param yloc Vertical position of starting point for splashing trees. * @note Trees are not smoothed. * @bug Function generates trees even if Micropolis::terrainTreeLevel is 0. */ void Micropolis::treeSplash(short xloc, short yloc) { short numTrees; if (terrainTreeLevel < 0) { numTrees = getRandom(150) + 50; } else { numTrees = getRandom(100 + (terrainTreeLevel * 2)) + 50; } Position treePos(xloc, yloc); while (numTrees > 0) { Direction2 dir = (Direction2)(DIR2_NORTH + getRandom(7)); treePos.move(dir); if (!treePos.testBounds()) { return; } if ((map[treePos.posX][treePos.posY] & LOMASK) == DIRT) { map[treePos.posX][treePos.posY] = WOODS | BLBNBIT; } numTrees--; } } /** Splash trees around the world. */ void Micropolis::doTrees() { short Amount, x, xloc, yloc; if (terrainTreeLevel < 0) { Amount = getRandom(100) + 50; } else { Amount = terrainTreeLevel + 3; } for (x = 0; x < Amount; x++) { xloc = getRandom(WORLD_W - 1); yloc = getRandom(WORLD_H - 1); treeSplash(xloc, yloc); } smoothTrees(); smoothTrees(); } void Micropolis::smoothRiver() { static short dx[4] = { -1, 0, 1, 0 }; static short dy[4] = { 0, 1, 0, -1 }; static short REdTab[16] = { 13 | BULLBIT, 13 | BULLBIT, 17 | BULLBIT, 15 | BULLBIT, 5 | BULLBIT, 2, 19 | BULLBIT, 17 | BULLBIT, 9 | BULLBIT, 11 | BULLBIT, 2, 13 | BULLBIT, 7 | BULLBIT, 9 | BULLBIT, 5 | BULLBIT, 2 }; short bitIndex, z, xTemp, yTemp; short temp, x, y; for (x = 0; x < WORLD_W; x++) { for (y = 0; y < WORLD_H; y++) { if (map[x][y] == REDGE) { bitIndex = 0; for (z = 0; z < 4; z++) { bitIndex = bitIndex << 1; xTemp = x + dx[z]; yTemp = y + dy[z]; if (testBounds(xTemp, yTemp) && ((map[xTemp][yTemp] & LOMASK) != DIRT) && (((map[xTemp][yTemp] & LOMASK) < WOODS_LOW) || ((map[xTemp][yTemp] & LOMASK) > WOODS_HIGH))) { bitIndex++; } } temp = REdTab[bitIndex & 15]; if ((temp != RIVER) && getRandom(1)) { temp++; } map[x][y] = temp; } } } } bool Micropolis::isTree(MapValue cell) { if ((cell & LOMASK) >= WOODS_LOW && (cell & LOMASK) <= WOODS_HIGH) { return true; } return false; } void Micropolis::smoothTrees() { short x, y; for (x = 0; x < WORLD_W; x++) { for (y = 0; y < WORLD_H; y++) { if (isTree(map[x][y])) { smoothTreesAt(x, y, false); } } } } /** Temporary function to prevent breaking a lot of code. */ void Micropolis::smoothTreesAt(int x, int y, bool preserve) { ToolEffects effects(this); smoothTreesAt(x, y, preserve, &effects); effects.modifyWorld(); } /** * Smooth trees at a position. */ void Micropolis::smoothTreesAt(int x, int y, bool preserve, ToolEffects *effects) { static short dx[4] = { -1, 0, 1, 0 }; static short dy[4] = { 0, 1, 0, -1 }; static const short treeTable[16] = { 0, 0, 0, 34, 0, 0, 36, 35, 0, 32, 0, 33, 30, 31, 29, 37, }; if (!isTree(effects->getMapValue(x, y))) { return; } int bitIndex = 0; int z; for (z = 0; z < 4; z++) { bitIndex = bitIndex << 1; int xTemp = x + dx[z]; int yTemp = y + dy[z]; if (testBounds(xTemp, yTemp) && isTree(effects->getMapValue(xTemp, yTemp))) { bitIndex++; } } int temp = treeTable[bitIndex & 15]; if (temp) { if (temp != WOODS) { if ((x + y) & 1) { temp = temp - 8; } } effects->setMapValue(x, y, temp | BLBNBIT); } else { if (!preserve) { effects->setMapValue(x, y, temp); } } } /** * Construct rivers. * @param terrainPos Coordinate to start making a river. */ void Micropolis::doRivers(const Position &terrainPos) { Direction2 riverDir; // Global direction of the river Direction2 terrainDir; // Local direction of the river riverDir = (Direction2)(DIR2_NORTH + (getRandom(3) * 2)); doBRiver(terrainPos, riverDir, riverDir); riverDir = rotate180(riverDir); terrainDir = doBRiver(terrainPos, riverDir, riverDir); riverDir = (Direction2)(DIR2_NORTH + (getRandom(3) * 2)); doSRiver(terrainPos, riverDir, terrainDir); } /** * Make a big river. * @param pos Start position of making a river. * @param riverDir Global direction of the river. * @param terrainDir Local direction of the terrain. * @return Last used local terrain direction. */ Direction2 Micropolis::doBRiver(const Position &riverPos, Direction2 riverDir, Direction2 terrainDir) { int rate1, rate2; if (terrainCurveLevel < 0) { rate1 = 100; rate2 = 200; } else { rate1 = terrainCurveLevel + 10; rate2 = terrainCurveLevel + 100; } Position pos(riverPos); while (testBounds(pos.posX + 4, pos.posY + 4)) { plopBRiver(pos); if (getRandom(rate1) < 10) { terrainDir = riverDir; } else { if (getRandom(rate2) > 90) { terrainDir = rotate45(terrainDir); } if (getRandom(rate2) > 90) { terrainDir = rotate45(terrainDir, 7); } } pos.move(terrainDir); } return terrainDir; } /** * Make a small river. * @param pos Start position of making a river. * @param riverDir Global direction of the river. * @param terrainDir Local direction of the terrain. * @return Last used local terrain direction. */ Direction2 Micropolis::doSRiver(const Position &riverPos, Direction2 riverDir, Direction2 terrainDir) { int rate1, rate2; if (terrainCurveLevel < 0) { rate1 = 100; rate2 = 200; } else { rate1 = terrainCurveLevel + 10; rate2 = terrainCurveLevel + 100; } Position pos(riverPos); while (testBounds(pos.posX + 3, pos.posY + 3)) { //printf("doSRiver %d %d td %d rd %d\n", pos.posX, pos.posY, terrainDir, riverDir); plopSRiver(pos); if (getRandom(rate1) < 10) { terrainDir = riverDir; } else { if (getRandom(rate2) > 90) { terrainDir = rotate45(terrainDir); } if (getRandom(rate2) > 90) { terrainDir = rotate45(terrainDir, 7); } } pos.move(terrainDir); } return terrainDir; } /** * Put \a mChar onto the map at position \a xLoc, \a yLoc if possible. * @param mChar Map value to put ont the map. * @param xLoc Horizontal position at the map to put \a mChar. * @param yLoc Vertical position at the map to put \a mChar. */ void Micropolis::putOnMap(MapValue mChar, short xLoc, short yLoc) { if (mChar == 0) { return; } if (!testBounds(xLoc, yLoc)) { return; } MapValue temp = map[xLoc][yLoc]; if (temp != DIRT) { temp = temp & LOMASK; if (temp == RIVER) { if (mChar != CHANNEL) { return; } } if (temp == CHANNEL) { return; } } map[xLoc][yLoc] = mChar; } /** * Put down a big river diamond-like shape. * @param pos Base coordinate of the blob (top-left position). */ void Micropolis::plopBRiver(const Position &pos) { short x, y; static MapValue BRMatrix[9][9] = { { 0, 0, 0, REDGE, REDGE, REDGE, 0, 0, 0 }, { 0, 0, REDGE, RIVER, RIVER, RIVER, REDGE, 0, 0 }, { 0, REDGE, RIVER, RIVER, RIVER, RIVER, RIVER, REDGE, 0 }, { REDGE, RIVER, RIVER, RIVER, RIVER, RIVER, RIVER, RIVER, REDGE }, { REDGE, RIVER, RIVER, RIVER, CHANNEL, RIVER, RIVER, RIVER, REDGE }, { REDGE, RIVER, RIVER, RIVER, RIVER, RIVER, RIVER, RIVER, REDGE }, { 0, REDGE, RIVER, RIVER, RIVER, RIVER, RIVER, REDGE, 0 }, { 0, 0, REDGE, RIVER, RIVER, RIVER, REDGE, 0, 0 }, { 0, 0, 0, REDGE, REDGE, REDGE, 0, 0, 0 }, }; for (x = 0; x < 9; x++) { for (y = 0; y < 9; y++) { putOnMap(BRMatrix[y][x], pos.posX + x, pos.posY + y); } } } /** * Put down a small river diamond-like shape. * @param pos Base coordinate of the blob (top-left position). */ void Micropolis::plopSRiver(const Position &pos) { short x, y; static MapValue SRMatrix[6][6] = { { 0, 0, REDGE, REDGE, 0, 0 }, { 0, REDGE, RIVER, RIVER, REDGE, 0 }, { REDGE, RIVER, RIVER, RIVER, RIVER, REDGE }, { REDGE, RIVER, RIVER, RIVER, RIVER, REDGE }, { 0, REDGE, RIVER, RIVER, REDGE, 0 }, { 0, 0, REDGE, REDGE, 0, 0 }, }; for (x = 0; x < 6; x++) { for (y = 0; y < 6; y++) { putOnMap(SRMatrix[y][x], pos.posX + x, pos.posY + y); } } } void Micropolis::smoothWater() { int x, y; MapTile tile; Direction2 dir; for (x = 0; x < WORLD_W; x++) { for (y = 0; y < WORLD_H; y++) { tile = map[x][y] & LOMASK; /* If (x, y) is water: */ if (tile >= WATER_LOW && tile <= WATER_HIGH) { const Position pos(x, y); for (dir = DIR2_BEGIN; dir < DIR2_END; dir = increment90(dir)) { /* If getting a tile off-map, condition below fails. */ // @note I think this may have been a bug, since it always uses DIR2_WEST instead of dir. //tile = getTileFromMap(pos, DIR2_WEST, WATER_LOW); tile = getTileFromMap(pos, dir, WATER_LOW); /* If nearest object is not water: */ if (tile < WATER_LOW || tile > WATER_HIGH) { map[x][y] = REDGE; /* set river edge */ break; // Continue with next tile } } } } } for (x = 0; x < WORLD_W; x++) { for (y = 0; y < WORLD_H; y++) { tile = map[x][y] & LOMASK; /* If water which is not a channel: */ if (tile != CHANNEL && tile >= WATER_LOW && tile <= WATER_HIGH) { bool makeRiver = true; // make (x, y) a river const Position pos(x, y); for (dir = DIR2_BEGIN; dir < DIR2_END; dir = increment90(dir)) { /* If getting a tile off-map, condition below fails. */ // @note I think this may have been a bug, since it always uses DIR2_WEST instead of dir. //tile = getTileFromMap(pos, DIR2_WEST, WATER_LOW); tile = getTileFromMap(pos, dir, WATER_LOW); /* If nearest object is not water: */ if (tile < WATER_LOW || tile > WATER_HIGH) { makeRiver = false; break; } } if (makeRiver) { map[x][y] = RIVER; /* make it a river */ } } } } for (x = 0; x < WORLD_W; x++) { for (y = 0; y < WORLD_H; y++) { tile = map[x][y] & LOMASK; /* If woods: */ if (tile >= WOODS_LOW && tile <= WOODS_HIGH) { const Position pos(x, y); for (dir = DIR2_BEGIN; dir < DIR2_END; dir = increment90(dir)) { /* If getting a tile off-map, condition below fails. */ // @note I think this may have been a bug, since it always uses DIR2_WEST instead of dir. //tile = getTileFromMap(pos, DIR2_WEST, WATER_LOW); tile = getTileFromMap(pos, dir, TILE_INVALID); if (tile == RIVER || tile == CHANNEL) { map[x][y] = REDGE; /* make it water's edge */ break; } } } } } } ////////////////////////////////////////////////////////////////////////