/* s_sim.c
 *
 * 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.
 */
#include "sim.h"


/* Simulation */


short ValveFlag;
short CrimeRamp, PolluteRamp ;
short RValve, CValve, IValve;
short ResCap, ComCap, IndCap;
short CashFlow;
float EMarket = 4.0;
short DisasterEvent;
short DisasterWait;
short ScoreType;
short ScoreWait;
short PwrdZCnt;
short unPwrdZCnt;
short NewPower; /* post */
short AvCityTax;
short Scycle = 0;
short Fcycle = 0;
short Spdcycle = 0;
short DoInitialEval = 0;
short MeltX, MeltY;

void CoalSmoke(int mx, int my);
void DoAirport(void);
void DrawStadium(int z);
void DoFire(void);
int GetBoatDis(void);
int DoBridge(void);
void DoRoad(void);
void DoRadTile(void);
void DoRail(void);
void MapScan(int x1, int x2);
void CollectTax(void);
void Take2Census(void);
void TakeCensus(void);
void ClearCensus(void);
void SetValves(void);
void SetCommonInits(void);
void SimLoadInit(void);
void InitSimMemory(void);
void DecROGMem(void);
void DecTrafficMem(void);
void Simulate(int mod16);

/* comefrom: doEditWindow scoreDoer doMapInFront graphDoer doNilEvent */
void SimFrame(void)
{
  if (SimSpeed == 0)
    return;

  if (++Spdcycle > 1023)
    Spdcycle = 0;

  if (SimSpeed == 1 && Spdcycle % 5)
    return;

  if (SimSpeed == 2 && Spdcycle % 3)
    return;

  if (++Fcycle > 1023) Fcycle = 0;
/*  if (InitSimLoad) Fcycle = 0; */
  Simulate(Fcycle & 15);
}


/* comefrom: SimFrame */
void
Simulate(int mod16)
{
  static short SpdPwr[4] = { 1,  2,  4,  5 };
  static short SpdPtl[4] = { 1,  2,  7, 17 };
  static short SpdCri[4] = { 1,  1,  8, 18 };
  static short SpdPop[4] = { 1,  1,  9, 19 };
  static short SpdFir[4] = { 1,  1, 10, 20 };
  short x;

  x = SimSpeed;
  if (x > 3) x = 3;

  switch (mod16)  {
    case 0:
      if (++Scycle > 1023) Scycle = 0;	/* this is cosmic */
      if (DoInitialEval) {
	DoInitialEval = 0;
	CityEvaluation();
      }
      CityTime++;
      AvCityTax += CityTax;		/* post */
      if (!(Scycle & 1)) SetValves();
      ClearCensus();
      break;
    case 1:
      MapScan(0, 1 * WORLD_X / 8);
      break;
    case 2:
      MapScan(1 * WORLD_X / 8, 2 * WORLD_X / 8);
      break;
    case 3:
      MapScan(2 * WORLD_X / 8, 3 * WORLD_X / 8);
      break;
    case 4:
      MapScan(3 * WORLD_X / 8, 4 * WORLD_X / 8);
      break;
    case 5:
      MapScan(4 * WORLD_X / 8, 5 * WORLD_X / 8);
      break;
    case 6:
      MapScan(5 * WORLD_X / 8, 6 * WORLD_X / 8);
      break;
    case 7:
      MapScan(6 * WORLD_X / 8, 7 * WORLD_X / 8);
      break;
    case 8:
      MapScan(7 * WORLD_X / 8, WORLD_X);
      break;
    case 9:
      if (!(CityTime % CENSUSRATE)) TakeCensus();
      if (!(CityTime % (CENSUSRATE * 12))) Take2Census();

      if (!(CityTime % TAXFREQ))  {
	CollectTax();
	CityEvaluation();
      }
      break;
    case 10:
      if (!(Scycle % 5)) DecROGMem();
      DecTrafficMem();
      NewMapFlags[TDMAP] = 1;
      NewMapFlags[RDMAP] = 1;
      NewMapFlags[ALMAP] = 1;
      NewMapFlags[REMAP] = 1;
      NewMapFlags[COMAP] = 1;
      NewMapFlags[INMAP] = 1;
      NewMapFlags[DYMAP] = 1;
      SendMessages();
      break;
    case 11:
      if (!(Scycle % SpdPwr[x])) {
	DoPowerScan();
	NewMapFlags[PRMAP] = 1;
	NewPower = 1; /* post-release change */
      }
      break;
    case 12:
      if (!(Scycle % SpdPtl[x])) PTLScan();
      break;
    case 13:
      if (!(Scycle % SpdCri[x])) CrimeScan();
      break;
    case 14:
      if (!(Scycle % SpdPop[x])) PopDenScan();
      break;
    case 15:
      if (!(Scycle % SpdFir[x])) FireAnalysis();
      DoDisasters();	
      break;
  }
}


/* comefrom: Simulate */
void
DoSimInit(void)
{
  Fcycle = 0;
  Scycle = 0;

  if (InitSimLoad == 2) 			/* if new city    */
    InitSimMemory();

  if (InitSimLoad == 1)  			/* if city just loaded  */
    SimLoadInit();

  SetValves();
  ClearCensus();
#if 1
  MapScan(0, WORLD_X); /* XXX are you sure ??? */
#endif
  DoPowerScan();
  NewPower = 1;		/* post rel */
  PTLScan();
  CrimeScan();
  PopDenScan();
  FireAnalysis();
  NewMap = 1;
  doAllGraphs();
  NewGraph = 1;
  TotalPop = 1;
  DoInitialEval = 1;
}


/* comefrom: SimLoadInit */
void
DoNilPower(void)
{
  register short x, y, z;

  for (x = 0; x < WORLD_X; x++)
    for (y = 0; y < WORLD_Y; y++) {
      z = Map[x][y];
      if (z & ZONEBIT) {
	SMapX = x;
	SMapY = y;
	CChr = z;
	SetZPower();
      }
    }
}


/* comefrom: Simulate */
void
DecTrafficMem(void)		/* tends to empty TrfDensity   */
{
  register short x, y, z;

  for (x = 0; x < HWLDX; x++)
    for (y = 0; y < HWLDY; y++)
      if ((z = TrfDensity[x][y])) {
	if (z > 24) {
	  if (z > 200) TrfDensity[x][y] = z - 34;
	  else TrfDensity[x][y] = z - 24;
	}
	else TrfDensity[x][y] = 0;
      }
}


/* comefrom: Simulate */
void
DecROGMem(void)			/* tends to empty RateOGMem   */
{
  register short x, y, z;

  for (x = 0; x < SmX; x++)
    for (y = 0; y < SmY; y++)	{
      z = RateOGMem[x][y];
      if (z == 0) continue;
      if (z > 0)  {
	--RateOGMem[x][y];
	if (z > 200) RateOGMem[x][y] = 200;    /* prevent overflow */
	continue;
      }
      if (z < 0)  {
	++RateOGMem[x][y];
	if (z < -200) RateOGMem[x][y] = -200;
      }
    }
}


/* comefrom: DoSimInit */
void
InitSimMemory(void)
{	
  register short x, z;

  z = 0;
  SetCommonInits();
  for (x = 0; x < 240; x++)  {
    ResHis[x] = z;
    ComHis[x] = z;
    IndHis[x] = z;
    MoneyHis[x] = 128;
    CrimeHis[x] = z;
    PollutionHis[x] = z;
  }
  CrimeRamp = z;
  PolluteRamp = z;
  TotalPop = z;
  RValve = z;
  CValve = z;
  IValve = z;
  ResCap = z;
  ComCap = z;
  IndCap = z;

  EMarket = 6.0;
  DisasterEvent = 0;
  ScoreType = 0;

  /* This clears powermem */
  PowerStackNum = z;		
  DoPowerScan();
  NewPower = 1;		/* post rel */	

  InitSimLoad = 0;
}


/* comefrom: DoSimInit */
void
SimLoadInit(void)
{
  static short DisTab[9] = { 0, 2, 10, 5, 20, 3, 5, 5, 2 * 48};
  static short ScoreWaitTab[9] = { 0, 30 * 48, 5 * 48, 5 * 48, 10 * 48,
				   5 * 48, 10 * 48, 5 * 48, 10 * 48 };
  register int z;

  z = 0;
  EMarket = (float)MiscHis[1];
  ResPop = MiscHis[2];
  ComPop = MiscHis[3];
  IndPop = MiscHis[4];
  RValve = MiscHis[5];
  CValve = MiscHis[6];
  IValve = MiscHis[7];
  CrimeRamp = MiscHis[10];
  PolluteRamp = MiscHis[11];
  LVAverage = MiscHis[12];
  CrimeAverage = MiscHis[13];
  PolluteAverage = MiscHis[14];
  GameLevel = MiscHis[15];

  if (CityTime < 0) CityTime = 0;
  if (!EMarket) EMarket = 4.0;
  if ((GameLevel > 2) || (GameLevel < 0)) GameLevel = 0;
  SetGameLevel(GameLevel);

  SetCommonInits();

  CityClass = MiscHis[16];
  CityScore = MiscHis[17];

  if ((CityClass > 5) || (CityClass < 0)) CityClass = 0;
  if ((CityScore > 999) || (CityScore < 1)) CityScore = 500;

  ResCap = 0;
  ComCap = 0;
  IndCap = 0;

  AvCityTax = (CityTime % 48) * 7;  /* post */

  for (z = 0; z < PWRMAPSIZE; z++)
    PowerMap[z] = ~0; /* set power Map */
  DoNilPower();

  if (ScenarioID > 8) ScenarioID = 0;

  if (ScenarioID) {
    DisasterEvent = ScenarioID;
    DisasterWait = DisTab[DisasterEvent];
    ScoreType = DisasterEvent;
    ScoreWait = ScoreWaitTab[DisasterEvent];
  } else {
    DisasterEvent = 0;
    ScoreType = 0;
  }

  RoadEffect = 32;
  PoliceEffect = 1000; /*post*/
  FireEffect = 1000;
  InitSimLoad = 0;
}


/* comefrom: InitSimMemory SimLoadInit */
void
SetCommonInits(void)
{
  EvalInit();
  RoadEffect = 32;
  PoliceEffect = 1000;
  FireEffect = 1000;
  TaxFlag = 0;
  TaxFund = 0;
/*
  if ((GameLevel > 2) || (GameLevel < 0)) GameLevel = 0;
  setGameLevel(GameLevel);
*/
}


/* comefrom: Simulate DoSimInit */
void
SetValves(void)
{
  static short TaxTable[21] = {
    200, 150, 120, 100, 80, 50, 30, 0, -10, -40, -100,
    -150, -200, -250, -300, -350, -400, -450, -500, -550, -600 };
  float Employment, Migration, Births, LaborBase, IntMarket;
  float Rratio, Cratio, Iratio, temp;
  float NormResPop, PjResPop, PjComPop, PjIndPop;
  register short z;

  MiscHis[1] = (short)EMarket;
  MiscHis[2] = ResPop;
  MiscHis[3] = ComPop;
  MiscHis[4] = IndPop;
  MiscHis[5] = RValve;
  MiscHis[6] = CValve;
  MiscHis[7] = IValve;
  MiscHis[10] = CrimeRamp;
  MiscHis[11] = PolluteRamp;
  MiscHis[12] = LVAverage;
  MiscHis[13] = CrimeAverage;
  MiscHis[14] = PolluteAverage;
  MiscHis[15] = GameLevel;
  MiscHis[16] = CityClass;
  MiscHis[17] = CityScore;

  NormResPop = ResPop / 8;
  LastTotalPop = TotalPop;
  TotalPop = NormResPop + ComPop + IndPop;

  if (NormResPop) Employment = ((ComHis[1] + IndHis[1]) / NormResPop);
  else Employment = 1;

  Migration = NormResPop * (Employment - 1);
  Births = NormResPop * (.02); 			/* Birth Rate  */
  PjResPop = NormResPop + Migration + Births;	/* Projected Res.Pop  */

  if ((temp = (ComHis[1] + IndHis[1]))) LaborBase = (ResHis[1] / temp);
  else LaborBase = 1;
  if (LaborBase > 1.3) LaborBase = 1.3;
  if (LaborBase < 0) LaborBase = 0;  /* LB > 1 - .1  */

  for (z = 0; z < 2; z++)
    temp = ResHis[z] + ComHis[z] + IndHis[z];
  IntMarket = (NormResPop + ComPop + IndPop) / 3.7;

  PjComPop = IntMarket * LaborBase;			

  z = GameLevel;			/* New ExtMarket */
  temp = 1;
  switch (z)  {
  case 0:
    temp = 1.2;
    break;
  case 1:
    temp = 1.1;
    break;
  case 2:
    temp = .98;
    break;
  }

  PjIndPop = IndPop * LaborBase * temp;
  if (PjIndPop < 5) PjIndPop = 5;

  if (NormResPop) Rratio = (PjResPop / NormResPop); /* projected -vs- actual */
  else Rratio = 1.3;
  if (ComPop) Cratio = (PjComPop / ComPop);
  else Cratio = PjComPop;
  if (IndPop) Iratio = (PjIndPop / IndPop);
  else Iratio = PjIndPop;

  if (Rratio > 2) Rratio = 2;
  if (Cratio > 2) Cratio = 2;
  if (Iratio > 2) Iratio = 2;

  z = CityTax + GameLevel;
  if (z > 20) z = 20;
  Rratio = ((Rratio -1) * 600) + TaxTable[z]; /* global tax/Glevel effects */
  Cratio = ((Cratio -1) * 600) + TaxTable[z];
  Iratio = ((Iratio -1) * 600) + TaxTable[z];

  if (Rratio > 0)		/* ratios are velocity changes to valves  */
    if (RValve <  2000) RValve += Rratio;
  if (Rratio < 0)
    if (RValve > -2000) RValve += Rratio;
  if (Cratio > 0)
    if (CValve <  1500) CValve += Cratio;
  if (Cratio < 0)
    if (CValve > -1500) CValve += Cratio;
  if (Iratio > 0)
    if (IValve <  1500) IValve += Iratio;
  if (Iratio < 0)
    if (IValve > -1500) IValve += Iratio;

  if (RValve >  2000) RValve =  2000;
  if (RValve < -2000) RValve = -2000;
  if (CValve >  1500) CValve =  1500;
  if (CValve < -1500) CValve = -1500;
  if (IValve >  1500) IValve =  1500;
  if (IValve < -1500) IValve = -1500;

  if ((ResCap) && (RValve > 0)) RValve = 0;	/* Stad, Prt, Airprt  */
  if ((ComCap) && (CValve > 0)) CValve = 0;
  if ((IndCap) && (IValve > 0)) IValve = 0;
  ValveFlag = 1;
}


/* comefrom: Simulate DoSimInit */
void
ClearCensus(void)
{
  register short x, y, z;

  z = 0;
  PwrdZCnt = z;
  unPwrdZCnt = z;
  FirePop = z;
  RoadTotal = z;
  RailTotal = z;
  ResPop = z;
  ComPop = z;
  IndPop = z;
  ResZPop = z;
  ComZPop = z;
  IndZPop = z;
  HospPop = z;
  ChurchPop = z;
  PolicePop = z;
  FireStPop = z;
  StadiumPop = z;
  CoalPop = z;
  NuclearPop = z;
  PortPop = z;
  APortPop = z;
  PowerStackNum = z;		/* Reset before Mapscan */
  for (x = 0; x < SmX; x++)
    for (y = 0; y < SmY; y++) {
      FireStMap[x][y] = z;
      PoliceMap[x][y] = z;
    }
}


/* comefrom: Simulate */
void
TakeCensus(void)
{
  short x;

  /* put census#s in Historical Graphs and scroll data  */
  ResHisMax = 0;
  ComHisMax = 0;
  IndHisMax = 0;
  for (x = 118; x >= 0; x--)	{
    if ((ResHis[x + 1] = ResHis[x]) > ResHisMax) ResHisMax = ResHis[x];
    if ((ComHis[x + 1] = ComHis[x]) > ComHisMax) ComHisMax = ComHis[x];
    if ((IndHis[x + 1] = IndHis[x]) > IndHisMax) IndHisMax = IndHis[x];
    CrimeHis[x + 1] = CrimeHis[x];
    PollutionHis[x + 1] = PollutionHis[x];
    MoneyHis[x + 1] = MoneyHis[x];
  }

  Graph10Max = ResHisMax;
  if (ComHisMax > Graph10Max) Graph10Max = ComHisMax;
  if (IndHisMax > Graph10Max) Graph10Max = IndHisMax;

  ResHis[0] = ResPop / 8;
  ComHis[0] = ComPop;
  IndHis[0] = IndPop;

  CrimeRamp += (CrimeAverage - CrimeRamp) / 4;
  CrimeHis[0] = CrimeRamp;

  PolluteRamp += (PolluteAverage - PolluteRamp) / 4;
  PollutionHis[0] = PolluteRamp;

  x = (CashFlow / 20) + 128;	/* scale to 0..255  */
  if (x < 0) x = 0;
  if (x > 255) x = 255;

  MoneyHis[0] = x;
  if (CrimeHis[0] > 255) CrimeHis[0] = 255;
  if (PollutionHis[0] > 255) PollutionHis[0] = 255;

  ChangeCensus(); /* XXX: if 10 year graph view */

  if (HospPop < (ResPop >>8)) NeedHosp = TRUE;
  if (HospPop > (ResPop >>8)) NeedHosp = -1;
  if (HospPop == (ResPop >>8)) NeedHosp = FALSE;

  if (ChurchPop < (ResPop >>8)) NeedChurch = TRUE;
  if (ChurchPop > (ResPop >>8)) NeedChurch = -1;
  if (ChurchPop == (ResPop >>8)) NeedChurch = FALSE;
}


/* comefrom: Simulate */
void
Take2Census(void)    /* Long Term Graphs */
{
  short x;

  Res2HisMax = 0;
  Com2HisMax = 0;
  Ind2HisMax = 0;
  for (x = 238; x >= 120; x--)	{
    if ((ResHis[x + 1] = ResHis[x]) > Res2HisMax) Res2HisMax = ResHis[x];
    if ((ComHis[x + 1] = ComHis[x]) > Com2HisMax) Com2HisMax = ComHis[x];
    if ((IndHis[x + 1] = IndHis[x]) > Ind2HisMax) Ind2HisMax = IndHis[x];
    CrimeHis[x + 1] = CrimeHis[x];
    PollutionHis[x + 1] = PollutionHis[x];
    MoneyHis[x + 1] = MoneyHis[x];
  }
  Graph120Max = Res2HisMax;
  if (Com2HisMax > Graph120Max) Graph120Max = Com2HisMax;
  if (Ind2HisMax > Graph120Max) Graph120Max = Ind2HisMax;

  ResHis[120] = ResPop / 8;
  ComHis[120] = ComPop;
  IndHis[120] = IndPop;
  CrimeHis[120] = CrimeHis[0] ;
  PollutionHis[120] = PollutionHis[0];
  MoneyHis[120] = MoneyHis[0];
  ChangeCensus(); /* XXX: if 120 year graph view */
}


/* comefrom: Simulate */
void
CollectTax(void)
{	
  static float RLevels[3] = { 0.7, 0.9, 1.2 };
  static float FLevels[3] = { 1.4, 1.2, 0.8 };
  /* short z; */

  CashFlow = 0;
  if (!TaxFlag) { /* if the Tax Port is clear  */
    /* XXX: do something with z */
    /* z = AvCityTax / 48; */  /* post */
    AvCityTax = 0;			
    PoliceFund = PolicePop * 100;
    FireFund   = FireStPop * 100;
    RoadFund   = (RoadTotal + (RailTotal * 2)) * RLevels[GameLevel];
    TaxFund = (((QUAD)TotalPop * LVAverage) / 120) *
      	      CityTax * FLevels[GameLevel];
    if (TotalPop) {	/* if there are people to tax  */
      CashFlow = TaxFund - (PoliceFund + FireFund + RoadFund);

      DoBudget();
    } else {
      RoadEffect = 32;
      PoliceEffect = 1000;
      FireEffect = 1000;
    }
  }
}


void
UpdateFundEffects(void)
{
  if (RoadFund)
    RoadEffect = (short)(((float)RoadSpend /
			  (float)RoadFund) * 32.0);
  else
    RoadEffect = 32;

  if (PoliceFund)
    PoliceEffect = (short)(((float)PoliceSpend /
			    (float)PoliceFund) * 1000.0);
  else
    PoliceEffect = 1000;

  if (FireFund)
    FireEffect = (short)(((float)FireSpend /
			  (float)FireFund) * 1000.0);
  else
    FireEffect = 1000;

  drawCurrPercents();
}


/* comefrom: Simulate DoSimInit */
void
MapScan(int x1, int x2)
{
  register short x, y;

  for (x = x1; x < x2; x++)  {
    for (y = 0; y < WORLD_Y; y++) {
      if ((CChr = Map[x][y])) {
	CChr9 = CChr & LOMASK;	/* Mask off status bits  */
	if (CChr9 >= FLOOD) {
	  SMapX = x;
	  SMapY = y;
	  if (CChr9 < ROADBASE) {
	    if (CChr9 >= FIREBASE) {
	      FirePop++;
	      if (!(Rand16() & 3)) DoFire();	/* 1 in 4 times */
	      continue;
	    }
	    if (CChr9 < RADTILE)  DoFlood();
	    else DoRadTile();
	    continue;
	  }

	  if (NewPower && (CChr & CONDBIT))
	    SetZPower();

	  if ((CChr9 >= ROADBASE) &&
	      (CChr9 < POWERBASE)) {
	    DoRoad();
	    continue;
	  }

	  if (CChr & ZONEBIT) {	/* process Zones */
	    DoZone();
	    continue;
	  }

	  if ((CChr9 >= RAILBASE) &&
	      (CChr9 < RESBASE)) {
	    DoRail();
	    continue;
	  }
	  if ((CChr9 >= SOMETINYEXP) &&
	      (CChr9 <= LASTTINYEXP))  /* clear AniRubble */
	    Map[x][y] = RUBBLE + (Rand16() & 3) + BULLBIT;
	}
      }
    }
  }
}


/* comefrom: MapScan */
void
DoRail(void)
{
  RailTotal++;
  GenerateTrain(SMapX, SMapY);
  if (RoadEffect < 30) /* Deteriorating  Rail  */
    if (!(Rand16() & 511))
      if (!(CChr & CONDBIT))
	if (RoadEffect < (Rand16() & 31)) {
	  if (CChr9 < (RAILBASE + 2))
	    Map[SMapX][SMapY] = RIVER;
	  else
	    Map[SMapX][SMapY] = RUBBLE + (Rand16() & 3) + BULLBIT;
	  return;
	}
}


/* comefrom: MapScan */
void
DoRadTile(void)
{
  if (!(Rand16() & 4095)) Map[SMapX][SMapY] = 0; /* Radioactive decay */
}


/* comefrom: MapScan */
void
DoRoad(void)
{
  register short Density, tden, z;
  static short DenTab[3] = { ROADBASE, LTRFBASE, HTRFBASE };

  RoadTotal++;
/*  GenerateBus(SMapX, SMapY); */
  if (RoadEffect < 30) /* Deteriorating Roads */
    if (!(Rand16() & 511))
      if (!(CChr & CONDBIT))
	if (RoadEffect < (Rand16() & 31)) {
	  if (((CChr9 & 15) < 2) || ((CChr9 & 15) == 15))
	    Map[SMapX][SMapY] = RIVER;
	  else
	    Map[SMapX][SMapY] = RUBBLE + (Rand16() & 3) + BULLBIT;
	  return;
	}

  if (!(CChr & BURNBIT)) { /* If Bridge */
    RoadTotal += 4;			
    if (DoBridge())  return;
  }
  if (CChr9 < LTRFBASE) tden = 0;
  else if (CChr9 < HTRFBASE) tden = 1;
  else {
    RoadTotal++;
    tden = 2;
  }

  Density = (TrfDensity[SMapX >>1][SMapY >>1]) >>6;  /* Set Traf Density  */
  if (Density > 1) Density--;
  if (tden != Density) { /* tden 0..2   */
    z = ((CChr9 - ROADBASE) & 15) + DenTab[Density];
    z += CChr & (ALLBITS - ANIMBIT);
    if (Density) z += ANIMBIT;
    Map[SMapX][SMapY] = z;
  }
}


/* comefrom: MapScan */
int
DoBridge(void)
{
  static short HDx[7] = { -2,  2, -2, -1,  0,  1,  2 };
  static short HDy[7] = { -1, -1,  0,  0,  0,  0,  0 };
  static short HBRTAB[7] = {
    HBRDG1 | BULLBIT, HBRDG3 | BULLBIT, HBRDG0 | BULLBIT,
    RIVER, BRWH | BULLBIT, RIVER, HBRDG2 | BULLBIT };
  static short HBRTAB2[7] = {
    RIVER, RIVER, HBRIDGE | BULLBIT, HBRIDGE | BULLBIT, HBRIDGE | BULLBIT,
    HBRIDGE | BULLBIT, HBRIDGE | BULLBIT };
  static short VDx[7] = {  0,  1,  0,  0,  0,  0,  1 };
  static short VDy[7] = { -2, -2, -1,  0,  1,  2,  2 };
  static short VBRTAB[7] = {
    VBRDG0 | BULLBIT, VBRDG1 | BULLBIT, RIVER, BRWV | BULLBIT,
    RIVER, VBRDG2 | BULLBIT, VBRDG3 | BULLBIT };
  static short VBRTAB2[7] = {
    VBRIDGE | BULLBIT, RIVER, VBRIDGE | BULLBIT, VBRIDGE | BULLBIT,
    VBRIDGE | BULLBIT, VBRIDGE | BULLBIT, RIVER };
  register int z, x, y, MPtem;

  if (CChr9 == BRWV) { /*  Vertical bridge close */
    if ((!(Rand16() & 3)) &&
	(GetBoatDis() > 340))
      for (z = 0; z < 7; z++) { /* Close  */
	x = SMapX + VDx[z];
	y = SMapY + VDy[z];
	if (TestBounds(x, y))
	  if ((Map[x][y] & LOMASK) == (VBRTAB[z] & LOMASK))
	    Map[x][y] = VBRTAB2[z];
      }
    return (TRUE);
  }
  if (CChr9 == BRWH) { /*  Horizontal bridge close  */
    if ((!(Rand16() & 3)) &&
	(GetBoatDis() > 340))
      for (z = 0; z < 7; z++) { /* Close  */
	x = SMapX + HDx[z];
	y = SMapY + HDy[z];
	if (TestBounds(x, y))
	  if ((Map[x][y] & LOMASK) == (HBRTAB[z] & LOMASK))
	    Map[x][y] = HBRTAB2[z];
      }
    return (TRUE);
  }

  if ((GetBoatDis() < 300) || (!(Rand16() & 7))) {
    if (CChr9 & 1) {
      if (SMapX < (WORLD_X - 1))
	if (Map[SMapX + 1][SMapY] == CHANNEL) { /* Vertical open */
	  for (z = 0; z < 7; z++) {
	    x = SMapX + VDx[z];
	    y = SMapY + VDy[z];
	    if (TestBounds(x, y))  {
	      MPtem = Map[x][y];
	      if ((MPtem == CHANNEL) ||
		  ((MPtem & 15) == (VBRTAB2[z] & 15)))
		Map[x][y] = VBRTAB[z];
	    }
	  }
	  return (TRUE);
	}
      return (FALSE);
    } else {
      if (SMapY > 0)
	if (Map[SMapX][SMapY - 1] == CHANNEL) { /* Horizontal open  */
	  for (z = 0; z < 7; z++) {
	    x = SMapX + HDx[z];
	    y = SMapY + HDy[z];
	    if (TestBounds(x, y)) {
	      MPtem = Map[x][y];
	      if (((MPtem & 15) == (HBRTAB2[z] & 15)) ||
		  (MPtem == CHANNEL))
		Map[x][y] = HBRTAB[z];
	    }
	  }
	  return (TRUE);
	}
      return (FALSE);
    }
  }
  return (FALSE);
}


int
GetBoatDis(void)
{
  register int dist, mx, my, dx, dy;
  SimSprite *sprite;

  dist = 99999;
  mx = (SMapX <<4) + 8;
  my = (SMapY <<4) + 8;

  for (sprite = sim->sprite; sprite != NULL; sprite = sprite->next) {
    if ((sprite->type == SHI) &&
	(sprite->frame != 0)) {
      if ((dx = sprite->x + sprite->x_hot - mx) < 0) dx = -dx;
      if ((dy = sprite->y + sprite->y_hot - my) < 0) dy = -dy;
      dx += dy;
      if (dx < dist) dist = dx;
    }
  }
  return (dist);
}


/* comefrom: MapScan */
void
DoFire(void)
{
  static short DX[4] = { -1,  0,  1,  0 };
  static short DY[4] = {  0, -1,  0,  1 };
  register short z, Xtem, Ytem, Rate, c;

  for (z = 0; z < 4; z++) {
    if (!(Rand16() & 7)) {
      Xtem = SMapX + DX[z];
      Ytem = SMapY + DY[z];
      if (TestBounds(Xtem, Ytem)) {
	c = Map[Xtem][Ytem];
	if (c & BURNBIT) {
	  if (c & ZONEBIT) {
	    FireZone(Xtem, Ytem, c);
	    if ((c & LOMASK) > IZB)  { /*  Explode  */
	      MakeExplosionAt((Xtem <<4) + 8, (Ytem <<4) + 8);
	    }
	  }
	  Map[Xtem][Ytem] = FIRE + (Rand16() & 3) + ANIMBIT;
	}
      }
    }
  }
  z = FireRate[SMapX >>3][SMapY >>3];
  Rate = 10;
  if (z) {
    Rate = 3;
    if (z > 20) Rate = 2;
    if (z > 100) Rate = 1;
  }
  if (!Rand(Rate))
    Map[SMapX][SMapY] = RUBBLE + (Rand16() & 3) + BULLBIT;
}


/* comefrom: DoFire MakeFlood */
void
FireZone(int Xloc, int Yloc, int ch)
{
  register short Xtem, Ytem;
  short x, y, XYmax;

  RateOGMem[Xloc >>3][Yloc >>3] -= 20;

  ch = ch & LOMASK;
  if (ch < PORTBASE)
    XYmax = 2;
  else
    if (ch == AIRPORT)
      XYmax = 5;
    else
      XYmax = 4;

  for (x = -1; x < XYmax; x++)
    for (y = -1; y < XYmax; y++) {
      Xtem = Xloc + x;
      Ytem = Yloc + y;
      if ((Xtem < 0) || (Xtem > (WORLD_X - 1)) ||
	  (Ytem < 0) || (Ytem > (WORLD_Y - 1)))
	continue;
      if ((short)(Map[Xtem][Ytem] & LOMASK) >= ROADBASE) /* post release */
	Map[Xtem][Ytem] |= BULLBIT;
    }
}


/* comefrom: DoSPZone DoHospChur */
void
RepairZone(short ZCent, short zsize)
{
  short cnt;
  register short x, y, ThCh;

  zsize--;
  cnt = 0;
  for (y = -1; y < zsize; y++)
    for (x = -1; x < zsize; x++) {
      int xx = SMapX + x;
      int yy = SMapY + y;
      cnt++;
      if (TestBounds(xx, yy)) {
	ThCh = Map[xx][yy];
	if (ThCh & ZONEBIT) continue;
	if (ThCh & ANIMBIT) continue;
	ThCh = ThCh & LOMASK;
	if ((ThCh < RUBBLE) || (ThCh >= ROADBASE)) {
	  Map[xx][yy] = ZCent - 3 - zsize + cnt + CONDBIT + BURNBIT;
	}
      }
    }
}


/* comefrom: DoZone */
void
DoSPZone(short PwrOn)
{
  static short MltdwnTab[3] = { 30000, 20000, 10000 };  /* simadj */
  register int z;

  switch (CChr9) {

  case POWERPLANT:
    CoalPop++;
    if (!(CityTime & 7))
      RepairZone(POWERPLANT, 4); /* post */
    PushPowerStack();
    CoalSmoke(SMapX, SMapY);
    return;

  case NUCLEAR:
    if (!NoDisasters && !Rand(MltdwnTab[GameLevel])) {
      DoMeltdown(SMapX, SMapY);
      return;
    }
    NuclearPop++;
    if (!(CityTime & 7))
      RepairZone(NUCLEAR, 4); /* post */
    PushPowerStack();
    return;

  case FIRESTATION:
    FireStPop++;
    if (!(CityTime & 7))
      RepairZone(FIRESTATION, 3); /* post */

    if (PwrOn)
      z = FireEffect;			/* if powered get effect  */
    else
      z = FireEffect >>1;		/* from the funding ratio  */

    if (!FindPRoad())
      z = z >>1;			/* post FD's need roads  */

    FireStMap[SMapX >>3][SMapY >>3] += z;
    return;

  case POLICESTATION:
    PolicePop++;
    if (!(CityTime & 7))
      RepairZone(POLICESTATION, 3); /* post */

    if (PwrOn)
      z = PoliceEffect;
    else
      z = PoliceEffect >>1;

    if (!FindPRoad())
      z = z >>1; /* post PD's need roads */

    PoliceMap[SMapX >>3][SMapY >>3] += z;
    return;

  case STADIUM:
    StadiumPop++;
    if (!(CityTime & 15))
      RepairZone(STADIUM, 4);
    if (PwrOn)
      if (!((CityTime + SMapX + SMapY) & 31)) {	/* post release */
	DrawStadium(FULLSTADIUM);
	Map[SMapX + 1][SMapY] = FOOTBALLGAME1 + ANIMBIT;
	Map[SMapX + 1][SMapY + 1] = FOOTBALLGAME2 + ANIMBIT;
      }
    return;

 case FULLSTADIUM:
    StadiumPop++;
    if (!((CityTime + SMapX + SMapY) & 7))	/* post release */
      DrawStadium(STADIUM);
    return;

 case AIRPORT:
    APortPop++;
    if (!(CityTime & 7))
      RepairZone(AIRPORT, 6);

    if (PwrOn) { /* post */
      if ((Map[SMapX + 1][SMapY - 1] & LOMASK) == RADAR)
	Map[SMapX + 1][SMapY - 1] = RADAR + ANIMBIT + CONDBIT + BURNBIT;
    } else
      Map[SMapX + 1][SMapY - 1] = RADAR + CONDBIT + BURNBIT;

    if (PwrOn)
      DoAirport();
    return;

 case PORT:
    PortPop++;
    if ((CityTime & 15) == 0) {
      RepairZone(PORT, 4);
    }
    if (PwrOn &&
	(GetSprite(SHI) == NULL)) {
      GenerateShip();
    }
    return;
  }
}


/* comefrom: DoSPZone */
void
DrawStadium(int z)
{
  register int x, y;

  z = z - 5;
  for (y = (SMapY - 1); y < (SMapY + 3); y++)
    for (x = (SMapX - 1); x < (SMapX + 3); x++)
      Map[x][y] = (z++) | BNCNBIT;
  Map[SMapX][SMapY] |= ZONEBIT | PWRBIT;
}


/* comefrom: DoSPZone */
void
DoAirport(void)
{
  if (!(Rand(5))) {
    GeneratePlane(SMapX, SMapY);
    return;
  }
  if (!(Rand(12)))
    GenerateCopter(SMapX, SMapY);
}


/* comefrom: DoSPZone */
void
CoalSmoke(int mx, int my)
{
  static short SmTb[4] = { COALSMOKE1, COALSMOKE2, COALSMOKE3, COALSMOKE4 };
  static short dx[4] = {  1,  2,  1,  2 };
  static short dy[4] = { -1, -1,  0,  0 };
  register short x;

  for (x = 0; x < 4; x++)
    Map[mx + dx[x]][my + dy[x]] =
      SmTb[x] | ANIMBIT | CONDBIT | PWRBIT | BURNBIT;
}


/* comefrom: DoSPZone MakeMeltdown */
void
DoMeltdown(int SX, int SY)
{
  register int x, y, z, t;

  MeltX = SX; MeltY = SY;

  MakeExplosion(SX - 1, SY - 1);
  MakeExplosion(SX - 1, SY + 2);
  MakeExplosion(SX + 2, SY - 1);
  MakeExplosion(SX + 2, SY + 2);

  for (x = (SX - 1); x < (SX + 3); x++)
    for (y = (SY - 1); y < (SY + 3); y++)
      Map[x][y] = FIRE + (Rand16() & 3) + ANIMBIT;

  for (z = 0; z < 200; z++)  {
    x = SX - 20 + Rand(40);
    y = SY - 15 + Rand(30);
    if ((x < 0) || (x >= WORLD_X) ||
	(y < 0) || (y >= WORLD_Y))
      continue;
    t = Map[x][y];
    if (t & ZONEBIT)
      continue;
    if ((t & BURNBIT) || (t == 0))
      Map[x][y] = RADTILE;
  }

  ClearMes();
  SendMesAt(-43, SX, SY);
}


#define RANDOM_RANGE 0xffff

short
Rand(short range)
{
  int maxMultiple, rnum;

  range++;
  maxMultiple = RANDOM_RANGE / range;
  maxMultiple *= range;
  while ((rnum = Rand16()) >= maxMultiple)
    continue;
  return (rnum % range);
}


int
Rand16(void)
{
  return (sim_rand());
}


int
Rand16Signed(void)
{
  int i = sim_rand();

  if (i > 32767) {
    i = 32767 - i;
  }
  return (i);
}


void
RandomlySeedRand()
{
  struct timeval time;

  gettimeofday(&time, NULL);

  SeedRand(time.tv_usec ^ time.tv_sec ^ sim_rand());
}

void
SeedRand(int seed)
{
  sim_srand(seed);
}
