Random City Generator Code

I’ve been meaning to release the code for my random city generator for a little while now but just hadn’t got round to it…. UNTIL NOW!

Now I’m not saying this is the best or most efficient method for doing this, this is just the method I’ve created. Below is an interactive version of the generator, click on it to create a new city.

[swf:https://defenestrate.me/wp-content/uploads/2013/04/City.swf 201 201]

As you can see, far from perfect but it does the job. There’s still the issue of two lanes of road running next to each other but this is something I can live with

City.as

package
{

	import city.Tile;

	import flash.display.Sprite;

	public class City extends Sprite
	{
		// Array of all the tiles
		private var _tiles:Array = new Array();
		// Visual display of the map
		private var _map:Sprite = new Sprite;

		// Width of the map to create
		private var _width:int = 50;
		// Height of the map
		private var _height:int = 50;

		// minimum length of a building
		private var _buildingMin:int = 5;
		// maximum length of a building
		private var _buildingMax:int = 10;

		// Exit points, can be predefined or randomly generated
		private var _exitPoints:Object = {
			//'north':new Array(10,20,30,40,50,60,70,80,90,100,110,120,130,140,150,160,170,180,190),
			'north':new Array(),
			'south':new Array(),
			'east':new Array(),
			'west':new Array()
		};

		private const SIDE_NORTH:int = 1;
		private const SIDE_SOUTH:int = 2;
		private const SIDE_EAST:int = 3;
		private const SIDE_WEST:int = 4;

		// The current side we're building up
		private var _activeSide:int;

		// The level away from the boundary
		private var _activeLevel:int = 0;

		// Array containing the buildings details
		private var _buildings:Array = new Array();

		public function City()
		{
			// Let's start from the top
			_activeSide = this.SIDE_NORTH

			addChild(_map);
			this.createMap();
			this.checkExits();
			this.process();

			// Just for cosmetics, move away from the window
			_map.x = 20;
			_map.y = 20;
		}

		private function createMap():void
		{
			// Creates a map of empty tiles based on the width / height
			for (var i:int = 0; i < this._height; i++) {
				this._tiles.push(new Array());
				for (var j:int = 0; j < this._width; j++) {
					var tile:Tile = new Tile();
					_map.addChild(tile);
					tile.x = j*4;
					tile.y = i*4;
					this._tiles&#91;i&#93;.push(tile);
				}
			}
		}

		// Checks to see if exits exist and randomly generates any missing
		private function checkExits():void
		{
			// Randomly generate the exits if they weren't predefined
			if (!this._exitPoints&#91;'north'&#93;) {
				this._exitPoints&#91;'north'&#93; = this.generateExits(this._width);
			}
			if (!this._exitPoints&#91;'south'&#93;) {
				this._exitPoints&#91;'south'&#93; = this.generateExits(this._width);
			}
			if (!this._exitPoints&#91;'east'&#93;) {
				this._exitPoints&#91;'east'&#93; = this.generateExits(this._height);
			}
			if (!this._exitPoints&#91;'west'&#93;) {
				this._exitPoints&#91;'west'&#93; = this.generateExits(this._height);
			}

			// Draw the exits on the map
			var i:int;
			for(i=0;i<this._exitPoints&#91;'north'&#93;.length;i++) {
				this._tiles&#91;0&#93;&#91;this._exitPoints&#91;'north'&#93;&#91;i&#93;&#93;.type = Tile.TYPE_ROAD;
			}
			for(i=0;i<this._exitPoints&#91;'south'&#93;.length;i++) {
				this._tiles&#91;this._height-1&#93;&#91;this._exitPoints&#91;'south'&#93;&#91;i&#93;&#93;.type = Tile.TYPE_ROAD;
			}
			for(i=0;i<this._exitPoints&#91;'east'&#93;.length;i++) {
				this._tiles&#91;this._exitPoints&#91;'east'&#93;&#91;i&#93;&#93;&#91;this._width-1&#93;.type = Tile.TYPE_ROAD;
			}
			for(i=0;i<this._exitPoints&#91;'west'&#93;.length;i++) {
				this._tiles&#91;this._exitPoints&#91;'west'&#93;&#91;i&#93;&#93;&#91;0&#93;.type = Tile.TYPE_ROAD;
			}

		}

		// Function to randomly generate the exits
		private function generateExits(length:int):Array
		{
			var arrReturn:Array = new Array();
			var tmpLoc:int = 0;
			while (tmpLoc < length) {
				tmpLoc += Math.floor( Math.random()*this._buildingMin+(this._buildingMax-this._buildingMin));
				if (tmpLoc < length-3) { 					arrReturn.push(tmpLoc); 				} 			} 			return arrReturn; 		} 		 		 		 		// Function to rotate around the boundary on an inwards spiral 		private function process():void 		{ 			var count:int = 4; 			while (count > 0) {
				// Scan the row and create buildings if needed
				this.scanSide();

				// Switch the active side once we reach the edge
				switch (this._activeSide) {
					case SIDE_NORTH:
						this._activeSide = SIDE_EAST;
						break;
					case SIDE_EAST:
						this._activeSide = SIDE_SOUTH;
						break;
					case SIDE_SOUTH:
						this._activeSide = SIDE_WEST;
						break;
					case SIDE_WEST:
						this._activeSide = SIDE_NORTH;
						break;
				}
				count--;

				// If we've covered all four sides, let's move in a level
				if (
					count == 0 &&
					this._activeLevel < (this._height/2)-2 &&
					this._activeLevel < (this._width/2)-2
				) {
					this._activeLevel++;
					count = 4;
				}

			}
		}

		// Finds empty tiles & populates with a building
		private function scanSide():void
		{
			var xLoc:int;
			var yLoc:int;
			switch (this._activeSide) {
				case SIDE_NORTH:
					for (xLoc=0;xLoc<this._width;xLoc++) {
						if (this._tiles&#91;this._activeLevel&#93;&#91;xLoc&#93;.type == Tile.TYPE_EMPTY ) {
							this.createBuilding(xLoc,this._activeLevel);
						}
					}
					break;

				case SIDE_EAST:
					for (yLoc=0;yLoc<this._height;yLoc++) { 						if (this._tiles&#91;yLoc&#93;&#91;this._width-1-this._activeLevel&#93;.type == Tile.TYPE_EMPTY ) { 							this.createBuilding(this._width-1-this._activeLevel,yLoc); 						} 					} 					break; 				 				case SIDE_SOUTH: 					for (xLoc=this._width-1;xLoc>=0;xLoc--) {
						if (this._tiles[this._height-1-this._activeLevel][xLoc].type == Tile.TYPE_EMPTY ) {
							this.createBuilding(xLoc,this._height-1-this._activeLevel);
						}
					}
					break;

				case SIDE_WEST:
					for (yLoc=(this._height-1);yLoc>=0;yLoc--) {
						if (this._tiles[yLoc][this._activeLevel].type == Tile.TYPE_EMPTY ) {
							this.createBuilding(this._activeLevel,yLoc);
						}
					}
					break;

			}
		}

		// This tile is empty, let's create a building
		private function createBuilding(xLoc:int,yLoc:int):void
		{
			var xPos:int = xLoc;
			var yPos:int = yLoc;

			// Let's check the first dimention

			while (this.isTileEmpty(xPos,yPos)) {
				if (this._activeSide == SIDE_NORTH) {
					xPos++;
				}else if (this._activeSide == SIDE_SOUTH) {
					xPos--;
				}else if (this._activeSide == SIDE_EAST) {
					yPos++;
				}else if (this._activeSide == SIDE_WEST) {
					yPos--;
				}
			}

			if (
				this._activeSide == SIDE_NORTH &&
				(xPos - xLoc) > 10
			) {
				xPos = xLoc + Math.floor( Math.random()*this._buildingMin+(this._buildingMax-this._buildingMin));

			}else if (
				this._activeSide == SIDE_SOUTH &&
				(xLoc - xPos) > 10
			) {
				xPos = xLoc - Math.floor( Math.random()*this._buildingMin+(this._buildingMax-this._buildingMin));

			}else if (
				this._activeSide == SIDE_EAST &&
				(yPos - yLoc) > 10
			) {
				yPos = yLoc + Math.floor( Math.random()*this._buildingMin+(this._buildingMax-this._buildingMin));

			}else if (
				this._activeSide == SIDE_WEST &&
				(yLoc - yPos) > 10

			) {
				yPos = yLoc - Math.floor( Math.random()*this._buildingMin+(this._buildingMax-this._buildingMin));
			}

			var tmpDimention:int;
			var direction:String;
			var length:int;

			if (xLoc != xPos) {
				tmpDimention = xPos;
				if(xLoc < xPos){
					direction = 'right';
					length = xPos-xLoc;
				}else{
					direction = 'left';
					length = xLoc-xPos;
				}
			}else{
				tmpDimention = yPos;
				if(yLoc < yPos){ 					direction = 'down'; 					length = yPos-yLoc; 				}else{ 					direction = 'up'; 					length = yLoc-yPos; 				} 			} 			 			xPos = xLoc; 			yPos = yLoc; 			 			 			// Let's check the second dimention 			 			while (this.isLineEmpty(xPos, yPos, direction, length)) { 				if (this._activeSide == SIDE_NORTH) { 					yPos++; 				}else if (this._activeSide == SIDE_SOUTH) { 					yPos--; 				}else if (this._activeSide == SIDE_EAST) { 					xPos--; 				}else if (this._activeSide == SIDE_WEST) { 					xPos++; 				} 			} 			 			if ( 				this._activeSide == SIDE_NORTH && 				(yPos - yLoc) > 10
			) {
				yPos = yLoc + Math.floor( Math.random()*this._buildingMin+(this._buildingMax-this._buildingMin));

			}else if (
				this._activeSide == SIDE_SOUTH &&
				(yLoc - yPos) > 10
			) {
				yPos = yLoc - Math.floor( Math.random()*this._buildingMin+(this._buildingMax-this._buildingMin));

			}else if (
				this._activeSide == SIDE_EAST &&
				(xLoc - xPos) > 10
			) {
				xPos = xLoc - Math.floor( Math.random()*this._buildingMin+(this._buildingMax-this._buildingMin));

			}else if (
				this._activeSide == SIDE_WEST &&
				(xPos - xLoc) > 10

			) {
				xPos = xLoc + Math.floor( Math.random()*this._buildingMin+(this._buildingMax-this._buildingMin));
			}

			if (xLoc != xPos) {
				yPos = tmpDimention;
			}else{
				xPos = tmpDimention;
			}

			var left:int,right:int,top:int,bottom:int;

			// let's get the dimentions
			// Left / Right
			if (xPos > xLoc) {
				left = xLoc;
				right = xPos-1;
			}else{
				left = xPos+1;
				right = xLoc;
			}

			// Top / Bottom
			if (yPos > yLoc) {
				top = yLoc;
				bottom = yPos-1;
			}else{
				top = yPos+1;
				bottom = yLoc;
			}

			// check to see if all tiles within this area are empty
			var allEmpty:Boolean = true;
			for(var $y:int=top;$y<=bottom;$y++){
				for(var $x:int=left;$x<=right;$x++){
					if (!this.isTileEmpty($x,$y)) {
						allEmpty = false;
					}
				}
			}

			// if all the tiles aren't empty, let's quit out
			if (!allEmpty) {
				return void;
			}

			//var building:Object = {'top':top,'right':right,'bottom':bottom,'left':left};
			this._buildings.push({'top':top,'right':right,'bottom':bottom,'left':left});

			// Let's change to building
			for($y=top;$y<=bottom;$y++){
				for($x=left;$x<=right;$x++){ 					this._tiles&#91;$y&#93;&#91;$x&#93;.type = Tile.TYPE_BUILDING; 					 					// make the roads 					//LEFT 					if ($x==left && (this._tiles&#91;$y&#93;&#91;$x-1&#93;) != undefined) { 						this._tiles&#91;$y&#93;&#91;$x-1&#93;.type = Tile.TYPE_ROAD; 					} 					//RIGHT 					if ($x==right && (this._tiles&#91;$y&#93;&#91;$x+1&#93;) != undefined) { 						this._tiles&#91;$y&#93;&#91;$x+1&#93;.type = Tile.TYPE_ROAD; 					} 					//TOP 					if ($y==top && (this._tiles&#91;$y-1&#93;) != undefined) { 						this._tiles&#91;$y-1&#93;&#91;$x&#93;.type = Tile.TYPE_ROAD; 					} 					//BOTTOM 					if ($y==bottom && (this._tiles&#91;$y+1&#93;) != undefined) { 						this._tiles&#91;$y+1&#93;&#91;$x&#93;.type = Tile.TYPE_ROAD; 					} 					//TOP LEFT 					if ( 						$x==left && (this._tiles&#91;$y&#93;&#91;$x-1&#93;) != undefined && 						$y==top && (this._tiles&#91;$y-1&#93;)!= undefined 					) { 						this._tiles&#91;$y-1&#93;&#91;$x-1&#93;.type = Tile.TYPE_ROAD; 					} 					//TOP RIGHT 					if ( 						$x==right && (this._tiles&#91;$y&#93;&#91;$x+1&#93;) != undefined && 						$y==top && (this._tiles&#91;$y-1&#93;) != undefined 					) { 						this._tiles&#91;$y-1&#93;&#91;$x+1&#93;.type = Tile.TYPE_ROAD; 					} 					//BOTTOM LEFT 					if ( 						$x==left && (this._tiles&#91;$y&#93;&#91;$x-1&#93;) != undefined && 						$y==bottom && (this._tiles&#91;$y+1&#93;) != undefined 					) { 						this._tiles&#91;$y+1&#93;&#91;$x-1&#93;.type = Tile.TYPE_ROAD; 					} 					//BOTTOM RIGHT 					if ( 						$x==right && (this._tiles&#91;$y&#93;&#91;$x+1&#93;) != undefined && 						$y==bottom && (this._tiles&#91;$y+1&#93;) != undefined 					) { 						this._tiles&#91;$y+1&#93;&#91;$x+1&#93;.type = Tile.TYPE_ROAD; 					} 				} 			} 		} 		 		private function isTileEmpty(xLoc:int,yLoc:int):Boolean 		{ 			if ( 				(this._tiles&#91;yLoc&#93;) != undefined && 				(this._tiles&#91;yLoc&#93;&#91;xLoc&#93;) != undefined && 				this._tiles&#91;yLoc&#93;&#91;xLoc&#93;.type == Tile.TYPE_EMPTY 			) { 				return true; 			} 			return false; 		} 		 		private function isLineEmpty(xLoc:int,yLoc:int,direction:String,length:int):Boolean 		{ 			var xPos:int = xLoc; 			var yPos:int = yLoc; 			 			while (length > 0) {
				if(!this.isTileEmpty(xPos,yPos)){
					return false;
				}
				switch (direction) {
					case 'left':
						xPos--;
						break;
					case 'right':
						xPos++;
						break;
					case 'up':
						yPos--;
						break;
					case 'down':
						yPos++;
						break;
				}
				length--;
			}
			return true;

		}

		public function get buildings():Array
		{
			return _buildings;
		}
	}
}

Tile.as

The Tile class is simply a graphical element to show different colours for the different types of square, white being buildings & black being roads

package city
{
	import flash.display.Sprite;
	public class Tile extends Sprite
	{

		public static const TYPE_EMPTY:int = 0;
		public static const TYPE_BUILDING:int = 1;
		public static const TYPE_ROAD:int = 2;

		private var _type:int;

		public function Tile() {
			// constructor code
			_type = TYPE_EMPTY;
			colour = 0xffffff;
		}

		public function set colour(val:uint):void
		{
			graphics.clear();
			graphics.lineStyle(1,0x000000);
			graphics.beginFill(val);
			graphics.drawRect(0,0,4,4);
			graphics.endFill();
		}

		public function get type():int
		{
			return this._type;
		}

		public function set type(value:int):void
		{
			this._type = value;
			if (value==TYPE_EMPTY) {
				colour = 0xffffff;
			}else if (value==TYPE_BUILDING) {
				colour = 0xefefef;
			}else if (value==TYPE_ROAD) {
				colour = 0x333333;
			}else {
				colour = 0x000000;
			}
		}

	}

}