// This file is part of MicropolisJ.
// Copyright (C) 2013 Jason Long
// Portions Copyright (C) 1989-2007 Electronic Arts Inc.
//
// MicropolisJ is free software; you can redistribute it and/or modify
// it under the terms of the GNU GPLv3, with additional terms.
// See the README file, included in this distribution, for details.

package micropolisj.engine;

import java.util.*;

import static micropolisj.engine.TileConstants.*;
import static micropolisj.engine.Micropolis.ZoneType;
import static micropolisj.engine.Animate.Smoke;

/**
 * Process individual tiles of the map for each cycle.
 * In each sim cycle each tile will get activated, and this
 * class contains the activation code.
 */
class MapScanner
{
	final Micropolis city;
	final Random PRNG;
	int xpos;
	int ypos;
	char cchr;
	char cchr9;

	MapScanner(Micropolis city)
	{
		this.city = city;
		this.PRNG = city.PRNG;
	}

	/**
	 * Activate the tile identified by xpos and ypos properties.
	 */
	public void scanTile()
	{
		if (isFire(cchr))
		{
			city.firePop++;
			if (PRNG.nextInt(4) == 0)
			{
				// one in four times
				doFire();
			}
			return;
		}
		else if (isFlood(cchr))
		{
			doFlood();
			return;
		}
		else if (isRadioactive(cchr))
		{
			doRadioactiveTile();
			return;
		}

		cchr9 = (char) (cchr & LOMASK);

		if (city.newPower && ((cchr & CONDBIT) != 0))
		{
			setZonePower();
		}

		if (isRoad(cchr))
		{
			doRoad();
			return;
		}

		if (isZoneCenter(cchr))
		{
			doZone();
			return;
		}

		if (isRail(cchr))
		{
			doRail();
			return;
		}

		if (isTinyExplosion(cchr))
		{
			// clear AniRubble
			city.setTile(xpos, ypos, (char)(RUBBLE + PRNG.nextInt(4) + BULLBIT));
		}
	}

	/**
	 * Called when the current tile is a radioactive tile.
	 */
	void doRadioactiveTile()
	{
		if (PRNG.nextInt(4096) == 0)
		{
			// radioactive decay
			city.setTile(xpos, ypos, DIRT);
		}
	}

	static int [] TRAFFIC_DENSITY_TAB = { ROADBASE, LTRFBASE, HTRFBASE };

	/**
	 * Called when the current tile is a road tile.
	 */
	void doRoad()
	{
		city.roadTotal++;

		if (city.roadEffect < 30)
		{
			// deteriorating roads
			if (PRNG.nextInt(512) == 0)
			{
				if ((cchr & CONDBIT) == 0)
				{
					if (city.roadEffect < PRNG.nextInt(32))
					{
						if (isOverWater(cchr))
							city.setTile(xpos, ypos, RIVER);
						else
							city.setTile(xpos, ypos, (char)(RUBBLE + PRNG.nextInt(4) + BULLBIT));
						return;
					}
				}
			}
		}

		if ((cchr & BURNBIT) == 0) //bridge
		{
			city.roadTotal += 4;
			if (doBridge())
				return;
		}

		int tden;
		if ((cchr & LOMASK) < LTRFBASE)
			tden = 0;
		else if ((cchr & LOMASK) < HTRFBASE)
			tden = 1;
		else {
			city.roadTotal++;
			tden = 2;
		}

		int trafficDensity = city.trfDensity[ypos/2][xpos/2];
		int newLevel = trafficDensity < 64 ? 0 :
			trafficDensity < 192 ? 1 : 2;
		
		assert newLevel >= 0 && newLevel < TRAFFIC_DENSITY_TAB.length;

		if (tden != newLevel)
		{
			int z = (((cchr & LOMASK) - ROADBASE) & 15) + TRAFFIC_DENSITY_TAB[newLevel];
			z += cchr & (ALLBITS - ANIMBIT);

			if (newLevel != 0)
				z |= ANIMBIT;

			city.setTile(xpos, ypos, (char) z);
		}
	}

	/**
	 * Called when the current tile is an active fire.
	 */
	void doFire()
	{
		final int [] DX = { 0, 1, 0, -1 };
		final int [] DY = { -1, 0, 1, 0 };

		for (int dir = 0; dir < 4; dir++)
		{
			if (PRNG.nextInt(8) == 0)
			{
				int xtem = xpos + DX[dir];
				int ytem = ypos + DY[dir];
				if (!city.testBounds(xtem, ytem))
					continue;

				int c = city.map[ytem][xtem];
				if ((c & BURNBIT) != 0) {
					if (isZoneCenter(c)) {
						city.killZone(xtem, ytem, c);
						if ((c & LOMASK) > IZB) { //explode
							city.makeExplosion(xtem, ytem);
						}
					}
					city.setTile(xtem, ytem, (char)(FIRE + PRNG.nextInt(4) + ANIMBIT));
				}
			}
		}

		int cov = city.fireRate[ypos/8][xpos/8];  //fire station coverage
		int rate = cov > 100 ? 1 :
			cov > 20 ? 2 :
			cov != 0 ? 3 : 10;

		if (PRNG.nextInt(rate+1) == 0) {
			city.setTile(xpos, ypos, (char)(RUBBLE + PRNG.nextInt(4) + BULLBIT));
		}
	}

	/**
	 * Called when the current tile is a flooding tile.
	 */
	void doFlood()
	{
		final int [] DX = { 0, 1, 0, -1 };
		final int [] DY = { -1, 0, 1, 0 };

		if (city.floodCnt != 0)
		{
			for (int z = 0; z < 4; z++)
			{
				if (PRNG.nextInt(8) == 0) {
					int xx = xpos + DX[z];
					int yy = ypos + DY[z];
					if (city.testBounds(xx, yy)) {
						int c = city.getTile(xx, yy);
						int t = c & LOMASK;
						if (((c & BURNBIT) != 0) || c == DIRT ||
							(t >= WOODS5 && t < FLOOD))
						{
							if (isZoneCenter(c)) {
								city.killZone(xx, yy, c);
							}
							city.setTile(xx, yy, (char)(FLOOD + PRNG.nextInt(3)));
						}
					}
				}
			}
		}
		else {
			if (PRNG.nextInt(16) == 0) {
				city.setTile(xpos, ypos, DIRT);
			}
		}
	}

	/**
	 * Called when the current tile is railroad.
	 */
	void doRail()
	{
		city.railTotal++;
		city.generateTrain(xpos, ypos);

		if (city.roadEffect < 30) { // deteriorating rail
			if (PRNG.nextInt(512) == 0) {
				if ((cchr & CONDBIT) == 0) {
					if (city.roadEffect < PRNG.nextInt(32)) {
						if (isOverWater(cchr)) {
							city.setTile(xpos,ypos,RIVER);
						} else {
							city.setTile(xpos,ypos,(char)(RUBBLE + PRNG.nextInt(4)+BULLBIT));
						}
					}
				}
			}
		}
	}

	/**
	 * Called when the current tile is a road bridge over water.
	 * Handles the draw bridge. For the draw bridge to appear,
	 * there must be a boat on the water, the boat must be
	 * within a certain distance of the bridge, it must be where
	 * the map generator placed 'channel' tiles (these are tiles
	 * that look just like regular river tiles but have a different
	 * numeric value), and you must be a little lucky.
	 *
	 * @return true if the draw bridge is open; false otherwise
	 */
	boolean doBridge()
	{
		final int HDx[] = { -2,  2, -2, -1,  0,  1,  2 };
		final int HDy[] = { -1, -1,  0,  0,  0,  0,  0 };
		final char HBRTAB[] = {
			HBRDG1 | BULLBIT,       HBRDG3 | BULLBIT,
			HBRDG0 | BULLBIT,       RIVER,
			BRWH | BULLBIT,         RIVER,
			HBRDG2 | BULLBIT };
		final char HBRTAB2[] = {
			RIVER,                  RIVER,
			HBRIDGE | BULLBIT,      HBRIDGE | BULLBIT,
			HBRIDGE | BULLBIT,      HBRIDGE | BULLBIT,
			HBRIDGE | BULLBIT };

		final int VDx[] = {  0,  1,  0,  0,  0,  0,  1 };
		final int VDy[] = { -2, -2, -1,  0,  1,  2,  2 };
		final char VBRTAB[] = {
			VBRDG0 | BULLBIT,       VBRDG1 | BULLBIT,
			RIVER,                  BRWV | BULLBIT,
			RIVER,                  VBRDG2 | BULLBIT,
			VBRDG3 | BULLBIT };
		final char VBRTAB2[] = {
			VBRIDGE | BULLBIT,      RIVER,
			VBRIDGE | BULLBIT,      VBRIDGE | BULLBIT,
			VBRIDGE | BULLBIT,      VBRIDGE | BULLBIT,
			RIVER };

		if (cchr9 == BRWV) {
			// vertical bridge, open
			if (PRNG.nextInt(4) == 0 && getBoatDis() > 340/16) {
				//close the bridge
				applyBridgeChange(VDx, VDy, VBRTAB, VBRTAB2);
			}
			return true;
		}
		else if (cchr9 == BRWH) {
			// horizontal bridge, open
			if (PRNG.nextInt(4) == 0 && getBoatDis() > 340/16) {
				// close the bridge
				applyBridgeChange(HDx, HDy, HBRTAB, HBRTAB2);
			}
			return true;
		}

		if (getBoatDis() < 300/16 && PRNG.nextInt(8) == 0) {
			if ((cchr & 1) != 0) {
				// vertical bridge
				if (xpos < city.getWidth()-1) {
					// look for CHANNEL tile to right of
					// bridge. the CHANNEL tiles are only
					// found in the very center of the
					// river
					if (city.getTile(xpos+1,ypos) == CHANNEL) {
						// vertical bridge, open it up
						applyBridgeChange(VDx, VDy, VBRTAB2, VBRTAB);
						return true;
					}
				}
				return false;
			}
			else {
				// horizontal bridge
				if (ypos > 0) {
					// look for CHANNEL tile just above
					// bridge. the CHANNEL tiles are only
					// found in the very center of the
					// river
					if (city.getTile(xpos, ypos-1) == CHANNEL) {
						// open it up
						applyBridgeChange(HDx, HDy, HBRTAB2, HBRTAB);
						return true;
					}
				}
				return false;
			}
		}

		return false;
	}

	/**
	 * Helper function for doBridge- it toggles the draw-bridge.
	 */
	private void applyBridgeChange(int [] Dx, int [] Dy, char [] fromTab, char [] toTab)
	{
		for (int z = 0; z < 7; z++) {
			int x = xpos + Dx[z];
			int y = ypos + Dy[z];
			if (city.testBounds(x,y)) {
				if ((city.map[y][x] & LOMASK) == (fromTab[z] & LOMASK) ||
					(city.map[y][x] == CHANNEL)
					) {
					city.setTile(x, y, toTab[z]);
				}
			}
		}
	}

	/**
	 * Calculate how far away the boat currently is from the
	 * current tile.
	 */
	int getBoatDis()
	{
		int dist = 99999;
		for (Sprite s : city.sprites)
		{
			if (s.isVisible() && s.kind == SpriteKind.SHI)
			{
				int x = s.x / 16;
				int y = s.y / 16;
				int d = Math.abs(xpos-x) + Math.abs(ypos-y);
				dist = Math.min(d, dist);
			}
		}
		return dist;
	}

	/**
	 * Called when the current tile is the key tile of an airport.
	 */
	void doAirport()
	{
		if (PRNG.nextInt(6) == 0) {
			city.generatePlane(xpos, ypos);
		}

		if (PRNG.nextInt(13) == 0) {
			city.generateCopter(xpos, ypos);
		}
	}

	/**
	 * Called when the current tile is the key tile of any zone.
	 */
	void doZone()
	{
		// set power bit in map, from powermap
		boolean zonePwrFlag = setZonePower();

		if (zonePwrFlag)
		{
			city.poweredZoneCount++;
		}
		else
		{
			city.unpoweredZoneCount++;
		}

		if (isSpecialZone(cchr))
		{
			doSpecialZone(zonePwrFlag);
			return;
		}

		if (isResidentialZone(cchr))
		{
			doResidential(zonePwrFlag);
			return;
		}

		if (isHospitalOrChurch(cchr))
		{
			doHospitalChurch(zonePwrFlag);
			return;
		}

		if (isCommercialZone(cchr))
		{
			doCommercial(zonePwrFlag);
			return;
		}

		assert isIndustrialZone(cchr);
		doIndustrial(zonePwrFlag);
		return;
	}

	boolean setZonePower()
	{
		// refresh cchr, cchr9, since this can get called after the
		// tile's been changed
		cchr = city.map[ypos][xpos];
		cchr9 = (char) (cchr & LOMASK);

		if (cchr9 == NUCLEAR ||
			cchr9 == POWERPLANT ||
			city.hasPower(xpos,ypos))
		{
			city.setTile(xpos, ypos, (char) (cchr | PWRBIT));
			return true;
		}
		else
		{
			city.setTile(xpos, ypos, (char) (cchr & (~PWRBIT)));
			return false;
		}
	}

	/**
	 * Place a 3x3 zone on to the map, centered on the current location.
	 * Note: nothing is done if part of this zone is off the edge
	 * of the map or is being flooded or radioactive.
	 *
	 * @param base The first/north-western tile value for this zone.
	 * @return true iff the zone was actually placed.
	 */
	boolean zonePlop(int base)
	{
		//FIXME- does this function have a caller that actually
		//pays attention to the return value?

		if (!city.testBounds(xpos-1, ypos-1))
			return false;
		if (!city.testBounds(xpos+1, ypos+1))
			return false;

		for (int y = ypos-1; y <= ypos+1; y++)
		{
			for (int x = xpos-1; x <= xpos+1; x++)
			{
				char c = (char)(city.map[y][x] & LOMASK);
				if (c >= FLOOD && c < ROADBASE) {
					// radioactive, on fire, or flooded
					return false;
				}
			}
		}

		for (int y = ypos-1; y <= ypos+1; y++)
		{
			for (int x = xpos-1; x <= xpos+1; x++)
			{
				city.setTile(x, y, (char)(base | BNCNBIT | (x == xpos && y == ypos ? ZONEBIT + BULLBIT : 0)));
				base++;
			}
		}

		setZonePower();
		return true;
	}

	/**
	 * Called when the current tile is the key tile of a "special" zone.
	 * @param powerOn indicates whether the building has power
	 */
	void doSpecialZone(boolean powerOn)
	{
		switch (cchr9)
		{
		case POWERPLANT:
			city.coalCount++;
			if ((city.cityTime % 8) == 0) {
				repairZone(POWERPLANT, 4);
			}

			city.powerPlants.add(new CityLocation(xpos,ypos));
			coalSmoke();
			return;

		case NUCLEAR:
			if (!city.noDisasters && PRNG.nextInt(city.MltdwnTab[city.gameLevel]+1) == 0) {
				city.doMeltdown(xpos, ypos);
				return;
			}

			city.nuclearCount++;
			if ((city.cityTime % 8) == 0) {
				repairZone(NUCLEAR, 4);
			}

			city.powerPlants.add(new CityLocation(xpos, ypos));
			return;

		case FIRESTATION:
		{
			city.fireStationCount++;
			if ((city.cityTime % 8) == 0) {
				repairZone(FIRESTATION, 3);
			}

			int z;
			if (powerOn) {
				z = city.fireEffect;  //if powered, get effect
			} else {
				z = city.fireEffect/2; // from the funding ratio
			}

			city.traffic.mapX = xpos;
			city.traffic.mapY = ypos;
			if (!city.traffic.findPerimeterRoad()) {
				z /= 2;
			}

			city.fireStMap[ypos/8][xpos/8] += z;
			return;
		}

		case POLICESTATION:
		{
			city.policeCount++;
			if ((city.cityTime % 8) == 0) {
				repairZone(POLICESTATION, 3);
			}

			int z;
			if (powerOn) {
				z = city.policeEffect;
			} else {
				z = city.policeEffect / 2;
			}

			city.traffic.mapX = xpos;
			city.traffic.mapY = ypos;
			if (!city.traffic.findPerimeterRoad()) {
				z /= 2;
			}

			city.policeMap[ypos/8][xpos/8] += z;
			return;
		}

		case STADIUM:
			city.stadiumCount++;
			if ((city.cityTime % 16) == 0) {
				repairZone(STADIUM, 4);
			}

			if (powerOn)
			{
				if (((city.cityTime + xpos + ypos) % 32) == 0) {
					drawStadium(FULLSTADIUM);
					city.setTile(xpos+1,ypos, (char)(FOOTBALLGAME1 | ANIMBIT));
					city.setTile(xpos+1,ypos+1,(char)(FOOTBALLGAME2 | ANIMBIT));
				}
			}
			return;

		case FULLSTADIUM:
			city.stadiumCount++;
			if (((city.cityTime + xpos + ypos) % 8) == 0) {
				drawStadium(STADIUM);
			}
			return;

		case AIRPORT:
			city.airportCount++;
			if ((city.cityTime % 8) == 0) {
				repairZone(AIRPORT, 6);
			}

			if (powerOn)
			{
				if ((city.map[ypos-1][xpos+1] & LOMASK) == RADAR) {
					city.setTile(xpos+1,ypos-1, (char)
					(RADAR + ANIMBIT + CONDBIT + BURNBIT)
					);
				}
			}
			else
			{
				city.setTile(xpos+1,ypos-1,(char)(RADAR + CONDBIT + BURNBIT));
			}

			if (powerOn) {
				doAirport();
			}
			return;

		case PORT:
			city.seaportCount++;
			if ((city.cityTime % 16) == 0) {
				repairZone(PORT, 4);
			}

			if (powerOn && !city.hasSprite(SpriteKind.SHI)) {
				city.generateShip();
			}
			return;

		default:
			// should not happen
			assert false;
		}
	}

	/**
	 * Place hospital or church if needed.
	 */
	void makeHospital()
	{
		if (city.needHospital > 0)
		{
			zonePlop(HOSPITAL - 4);
			city.needHospital = 0;
		}

//FIXME- should be 'else if'
		if (city.needChurch > 0)
		{
			zonePlop(CHURCH - 4);
			city.needChurch = 0;
		}
	}

	/**
	 * Called when the current tile is the key tile of a
	 * hospital or church.
	 * @param powerOn indicates whether the building has power
	 */
	void doHospitalChurch(boolean powerOn)
	{
		if (cchr9 == HOSPITAL)
		{
			city.hospitalCount++;

			if (city.cityTime % 16 == 0)
			{
				repairZone(HOSPITAL, 3);
			}
			if (city.needHospital == -1)  //too many hospitals
			{
				if (PRNG.nextInt(21) == 0)
				{
					zonePlop(RESBASE);
				}
			}
		}
		else if (cchr9 == CHURCH)
		{
			city.churchCount++;

			if (city.cityTime % 16 == 0)
			{
				repairZone(CHURCH, 3);
			}
			if (city.needChurch == -1) //too many churches
			{
				if (PRNG.nextInt(21) == 0)
				{
					zonePlop(RESBASE);
				}
			}
		}
	}

	/**
	 * Regenerate the tiles that make up the zone, repairing from
	 * fire, etc.
	 * Only tiles that are not rubble, radioactive, flooded, or
	 * on fire will be regenerated.
	 * @param zoneCenter the tile value for the "center" tile of the zone
	 * @param zoneSize integer (3-6) indicating the width/height of
	 * the zone.
	 */
	void repairZone(char zoneCenter, int zoneSize)
	{
		// from the given center tile, figure out what the
		// northwest tile should be
		int zoneBase = zoneCenter - 1 - zoneSize;

		for (int y = 0; y < zoneSize; y++)
		{
			for (int x = 0; x < zoneSize; x++, zoneBase++)
			{
				int xx = xpos - 1 + x;
				int yy = ypos - 1 + y;

				if (city.testBounds(xx, yy))
				{
					int thCh = city.map[yy][xx];
					if (isZoneCenter(thCh)) {
						continue;
					}

					if ((thCh & ANIMBIT) != 0)
						continue;

					thCh &= LOMASK;
					if (thCh < RUBBLE || thCh >= ROADBASE)
					{  //not rubble, radiactive, on fire or flooded

						city.setTile(xx,yy,(char)
						(zoneBase+CONDBIT+BURNBIT)
						);
					}
				}
			}
		}
	}

	/**
	 * Called when the current tile is the key tile of a commercial
	 * zone.
	 * @param powerOn indicates whether the building has power
	 */
	void doCommercial(boolean powerOn)
	{
		city.comZoneCount++;

		int tpop = commercialZonePop(cchr);
		city.comPop += tpop;

		int trafficGood;
		if (tpop > PRNG.nextInt(6))
		{
			trafficGood = city.makeTraffic(xpos, ypos, ZoneType.COMMERCIAL);
		}
		else
		{
			trafficGood = 1;
		}

		if (trafficGood == -1)
		{
			int value = getCRValue();
			doCommercialOut(tpop, value);
			return;
		}

		if (PRNG.nextInt(8) == 0)
		{
			int locValve = evalCommercial(trafficGood);
			int zscore = city.comValve + locValve;

			if (!powerOn)
				zscore = -500;

			if (trafficGood != 0 &&
				zscore > -350 &&
				zscore - 26380 > (PRNG.nextInt(0x10000)-0x8000))
			{
				int value = getCRValue();
				doCommercialIn(tpop, value);
				return;
			}

			if (zscore < 350 && zscore + 26380 < (PRNG.nextInt(0x10000)-0x8000))
			{
				int value = getCRValue();
				doCommercialOut(tpop, value);
			}
		}
	}

	/**
	 * Animate tiles in an industrial zone.
	 * Note: pollution is not accumulated here; it is instead
	 * accumulated in ptlScan(), by adding up each tile's
	 * pollution value.
	 *
	 * @param powerOn indicates whether the building has power
	 */
	void setSmoke(boolean powerOn)
	{
		int cchr9 = cchr & LOMASK;

		if (cchr9 < IZB)
			return;

		int z = ((cchr9 - IZB) / 8) % 8;
		if (Smoke.AniThis[z])
		{
			int xx = xpos + Smoke.DX1[z];
			int yy = ypos + Smoke.DY1[z];

			if (city.testBounds(xx, yy))
			{
				int t = city.map[yy][xx] & LOMASK;

				if (powerOn) {
					if (t == Smoke.AniTabC[z]) //expected non-animated tile
					{
						city.setTile(xx,yy,(char)(Smoke.ASCBIT | (SMOKEBASE + Smoke.AniTabA[z])));
					}
				}
				else {
					if (t > Smoke.AniTabC[z]) {
						city.setTile(xx,yy,(char)(Smoke.REGBIT | Smoke.AniTabC[z]));
					}
				}
			}

			xx = xpos + Smoke.DX2[z];
			yy = ypos + Smoke.DY2[z];
			if (city.testBounds(xx, yy) && !(Smoke.DX1[z] == Smoke.DX2[z] && Smoke.DY1[z] == Smoke.DY2[z]))
			{
				int t = city.map[yy][xx] & LOMASK;
				if (powerOn) {
					if (t == Smoke.AniTabD[z]) {
						city.setTile(xx,yy,(char)(Smoke.ASCBIT | (SMOKEBASE + Smoke.AniTabB[z])));
					}
				}
				else {
					if (t > Smoke.AniTabD[z]) {
						city.setTile(xx,yy,(char)(Smoke.REGBIT | Smoke.AniTabD[z]));
					}
				}
			}
		}
	}

	/**
	 * Called when the current tile is the key tile of an
	 * industrial zone.
	 * @param powerOn indicates whether the building has power
	 */
	void doIndustrial(boolean powerOn)
	{
		city.indZoneCount++;
		setSmoke(powerOn);

		int tpop = industrialZonePop(cchr);
		city.indPop += tpop;

		int trafficGood;
		if (tpop > PRNG.nextInt(6))
		{
			trafficGood = city.makeTraffic(xpos, ypos, ZoneType.INDUSTRIAL);
		}
		else
		{
			trafficGood = 1;
		}

		if (trafficGood == -1)
		{
			doIndustrialOut(tpop, PRNG.nextInt(2));
			return;
		}

		if (PRNG.nextInt(8) == 0)
		{
			int locValve = evalIndustrial(trafficGood);
			int zscore = city.indValve + locValve;

			if (!powerOn)
				zscore = -500;

			if (zscore > -350 &&
				zscore - 26380 > (PRNG.nextInt(0x10000)-0x8000))
			{
				int value = PRNG.nextInt(2);
				doIndustrialIn(tpop, value);
				return;
			}

			if (zscore < 350 && zscore + 26380 < (PRNG.nextInt(0x10000)-0x8000))
			{
				int value = PRNG.nextInt(2);
				doIndustrialOut(tpop, value);
			}
		}
	}

	/**
	 * Called when the current tile is the key tile of a
	 * residential zone.
	 * @param powerOn indicates whether the building has power
	 */
	void doResidential(boolean powerOn)
	{
		city.resZoneCount++;

		int tpop; //population of this zone
		if (cchr9 == FREEZ)
		{
			tpop = city.doFreePop(xpos, ypos);
		}
		else
		{
			tpop = residentialZonePop(cchr);
		}

		city.resPop += tpop;

		int trafficGood;
		if (tpop > PRNG.nextInt(36))
		{
			trafficGood = city.makeTraffic(xpos, ypos, ZoneType.RESIDENTIAL);
		}
		else
		{
			trafficGood = 1;
		}

		if (trafficGood == -1)
		{
			int value = getCRValue();
			doResidentialOut(tpop, value);
			return;
		}

		if (cchr9 == FREEZ || PRNG.nextInt(8) == 0)
		{
			int locValve = evalResidential(trafficGood);
			int zscore = city.resValve + locValve;

			if (!powerOn)
				zscore = -500;

			if (zscore > -350 && zscore - 26380 > (PRNG.nextInt(0x10000)-0x8000))
			{
				if (tpop == 0 && PRNG.nextInt(4) == 0)
				{
					makeHospital();
					return;
				}

				int value = getCRValue();
				doResidentialIn(tpop, value);
				return;
			}

			if (zscore < 350 && zscore + 26380 < (PRNG.nextInt(0x10000)-0x8000))
			{
				int value = getCRValue();
				doResidentialOut(tpop, value);
			}
		}
	}

	/**
	 * Consider the value of building a single-lot house at certain
	 * coordinates.
	 * @return integer; positive number indicates good place for
	 * house to go; zero or a negative number indicates a bad place.
	 */
	int evalLot(int x, int y)
	{
		// test for clear lot
		int tmp = city.getTile(x,y) & LOMASK;

		if (tmp != DIRT && (tmp < RESBASE || tmp > RESBASE+8))
		{
			return -1;
		}

		int score = 1;

		final int [] DX = { 0, 1, 0, -1 };
		final int [] DY = { -1, 0, 1, 0 };
		for (int z = 0; z < 4; z++)
		{
			int xx = x + DX[z];
			int yy = y + DY[z];

			if (city.testBounds(xx, yy) &&
				city.map[yy][xx] != DIRT &&
				((city.map[yy][xx] & LOMASK) <= LASTROAD)) //look for road
			{
				score++;
			}
		}

		return score;
	}

	/**
	 * Build a single-lot house on the current residential zone.
	 */
	private void buildHouse(int value)
	{
		assert value >= 0 && value <= 3;

		final int [] ZeX = { 0, -1, 0, 1, -1, 1, -1, 0, 1 };
		final int [] ZeY = { 0, -1, -1, -1, 0, 0, 1, 1, 1 };

		int bestLoc = 0;
		int hscore = 0;

		for (int z = 1; z < 9; z++)
		{
			int xx = xpos + ZeX[z];
			int yy = ypos + ZeY[z];

			if (city.testBounds(xx, yy))
			{
				int score = evalLot(xx, yy);

				if (score != 0)
				{
					if (score > hscore)
					{
						hscore = score;
						bestLoc = z;
					}

					if ((score == hscore) && PRNG.nextInt(8) == 0)
					{
						bestLoc = z;
					}
				}
			}
		}

		if (bestLoc != 0)
		{
			int xx = xpos + ZeX[bestLoc];
			int yy = ypos + ZeY[bestLoc];
			int houseNumber = value * 3 + PRNG.nextInt(3);
			assert houseNumber >= 0 && houseNumber < 12;

			assert city.testBounds(xx, yy);
			city.setTile(xx, yy, (char)(HOUSE + houseNumber + BLBNCNBIT));
		}
	}

	private void doCommercialIn(int pop, int value)
	{
		int z = city.landValueMem[ypos/2][xpos/2] / 32;
		if (pop > z)
			return;

		if (pop < 5)
		{
			comPlop(pop, value);
			adjustROG(8);
		}
	}

	private void doIndustrialIn(int pop, int value)
	{
		if (pop < 4)
		{
			indPlop(pop, value);
			adjustROG(8);
		}
	}

	private void doResidentialIn(int pop, int value)
	{
		assert value >= 0 && value <= 3;

		int z = city.pollutionMem[ypos/2][xpos/2];
		if (z > 128)
			return;

		int cchr9 = cchr & LOMASK;
		if (cchr9 == FREEZ)
		{
			if (pop < 8)
			{
				buildHouse(value);
				adjustROG(1);
				return;
			}

			if (city.getPopulationDensity(xpos, ypos) > 64)
			{
				residentialPlop(0, value);
				adjustROG(8);
				return;
			}
			return;
		}

		if (pop < 40)
		{
			residentialPlop(pop / 8 - 1, value);
			adjustROG(8);
		}
	}

	void comPlop(int density, int value)
	{
		int base = (value * 5 + density) * 9 + CZB - 4;
		zonePlop(base);
	}

	void indPlop(int density, int value)
	{
		int base = (value * 4 + density) * 9 + (IZB - 4);
		zonePlop(base);
	}

	void residentialPlop(int density, int value)
	{
		int base = (value * 4 + density) * 9 + RZB - 4;
		zonePlop(base);
	}

	private void doCommercialOut(int pop, int value)
	{
		if (pop > 1)
		{
			comPlop(pop-2, value);
			adjustROG(-8);
		}
		else if (pop == 1)
		{
			zonePlop(COMBASE);
			adjustROG(-8);
		}
	}

	private void doIndustrialOut(int pop, int value)
	{
		if (pop > 1)
		{
			indPlop(pop-2, value);
			adjustROG(-8);
		}
		else if (pop == 1)
		{
			zonePlop(INDCLR-4);
			adjustROG(-8);
		}
	}

	private void doResidentialOut(int pop, int value)
	{
		assert value >= 0 && value < 4;

		final char [] Brdr = { 0, 3, 6, 1, 4, 7, 2, 5, 8 };

		if (pop == 0)
			return;

		if (pop > 16)
		{
			// downgrade to a lower-density full-size residential zone
			residentialPlop((pop-24) / 8, value);
			adjustROG(-8);
			return;
		}

		if (pop == 16)
		{
			// downgrade from full-size zone to 8 little houses

			city.setTile(xpos, ypos, (char)(FREEZ | BLBNCNBIT | ZONEBIT));
			for (int x = xpos-1; x <= xpos+1; x++)
			{
				for (int y = ypos-1; y <= ypos+1; y++)
				{
					if (city.testBounds(x,y))
					{
						if (!(x == xpos && y == ypos))
						{
							// pick a random small house
							int houseNumber = value * 3 + PRNG.nextInt(3);
							city.setTile(x, y, (char) (HOUSE + houseNumber + BLBNCNBIT));
						}
					}
				}
			}

			adjustROG(-8);
			return;
		}

		if (pop < 16)
		{
			// remove one little house
			adjustROG(-1);
			int z = 0;

			for (int x = xpos-1; x <= xpos+1; x++)
			{
				for (int y = ypos-1; y <= ypos+1; y++)
				{
					if (city.testBounds(x,y))
					{
						int loc = city.map[y][x] & LOMASK;
						if (loc >= LHTHR && loc <= HHTHR)
						{ //little house
							city.setTile(x, y, (char)(Brdr[z] + BLBNCNBIT + FREEZ - 4));
							return;
						}
					}
					z++;
				}
			}
		}
	}

	/**
	 * Evaluates the zone value of the current commercial zone location.
	 * @return an integer between -3000 and 3000
	 * Same meaning as evalResidential.
	 */
	int evalCommercial(int traf)
	{
		if (traf < 0)
			return -3000;

		return city.comRate[ypos/8][xpos/8];
	}

	/**
	 * Evaluates the zone value of the current industrial zone location.
	 * @return an integer between -3000 and 3000.
	 * Same meaning as evalResidential.
	 */
	int evalIndustrial(int traf)
	{
		if (traf < 0)
			return -1000;
		else
			return 0;
	}

	/**
	 * Evaluates the zone value of the current residential zone location.
	 * @return an integer between -3000 and 3000. The higher the
	 * number, the more likely the zone is to GROW; the lower the
	 * number, the more likely the zone is to SHRINK.
	 */
	int evalResidential(int traf)
	{
		if (traf < 0)
			return -3000;

		int value = city.landValueMem[ypos/2][xpos/2];
		value -= city.pollutionMem[ypos/2][xpos/2];

		if (value < 0)
			value = 0;    //cap at 0
		else
			value *= 32;

		if (value > 6000)
			value = 6000; //cap at 6000

		return value - 3000;
	}

	/**
	 * Gets the land-value class (0-3) for the current
	 * residential or commercial zone location.
	 * @return integer from 0 to 3, 0 is the lowest-valued
	 * zone, and 3 is the highest-valued zone.
	 */
	int getCRValue()
	{
		int lval = city.landValueMem[ypos/2][xpos/2];
		lval -= city.pollutionMem[ypos/2][xpos/2];

		if (lval < 30)
			return 0;

		if (lval < 80)
			return 1;

		if (lval < 150)
			return 2;

		return 3;
	}

	/**
	 * Process the coal powerplant animation.
	 * Note: pollution is not accumulated here; see ptlScan()
	 * instead.
	 */
	void coalSmoke()
	{
		final int [] SmTb = { COALSMOKE1, COALSMOKE2, COALSMOKE3, COALSMOKE4 };
		final int [] dx = { 1, 2, 1, 2 };
		final int [] dy = { -1, -1, 0, 0 };

		for (int z = 0; z < 4; z++) {
			int tile = city.getTile(xpos+dx[z], ypos+dy[z]) & LOMASK;
			if (tile >= COALBASE && tile < COALBASE + 4*4) {
				city.setTile(xpos + dx[z], ypos + dy[z],
				(char) (SmTb[z] | ANIMBIT | CONDBIT | PWRBIT | BURNBIT)
				);
			}
		}
	}

	/**
	 * Record a zone's population change to the rate-of-growth
	 * map.
	 * An adjustment of +/- 1 corresponds to one little house.
	 * An adjustment of +/- 8 corresponds to a full-size zone.
	 *
	 * @param amount the positive or negative adjustment to record.
	 */
	void adjustROG(int amount)
	{
		city.rateOGMem[ypos/8][xpos/8] += 4*amount;
	}

	/**
	 * Place tiles for a stadium (full or empty).
	 * @param zoneCenter either STADIUM or FULLSTADIUM
	 */
	void drawStadium(int zoneCenter)
	{
		int zoneBase = zoneCenter - 1 - 4;

		for (int y = 0; y < 4; y++)
		{
			for (int x = 0; x < 4; x++, zoneBase++)
			{
				city.setTile(xpos - 1 + x, ypos - 1 + y,
					(char) (zoneBase | BNCNBIT | (x == 1 && y == 1 ? (ZONEBIT|PWRBIT) : 0)));
			}
		}
	}

}
