Train Game: 2D Path Logic

I begin with the 2D logic that handles the path formation in the game. The track is made out of tiles inside a grid, formed by a data array, which in this example was hard coded inside the main document class. Let me show you a sample of this data:

_pathData = [
 
 
 
	//ROW 5
 
	{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_TURN_UP_LEFT, Tile.TILE_VERTICAL, Tile.TILE_TURN_DOWN_LEFT, Tile.TILE_HORIZONTAL, Tile.TILE_TURN_DOWN_RIGHT, Tile.TILE_VERTICAL, Tile.TILE_TURN_UP_RIGHT, Tile.TILE_HORIZONTAL], target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_TURN_DOWN_LEFT],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
	{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
	//ROW 6
 
	//...

All I need for this particular game is the type of track each tile holds and what kind of target it is, meaning what happens when the train reaches it. The type of track is an array. If it holds more than one value than the track is a turn button. For this game I use only one type of turn button, but there are many possibilities here.

One important aspect of the way the data is organized is that the TILE is only concerned with its points, it knows nothing of the direction in which the train is traveling. Each tile contains two points. So the HORIZONTAL tile will have a point on the left and another on the right side of the tile. And so when in TYPE I have something like Tile.TILE_TURN_DOWN_RIGHT, it does not mean direction, it only means that in this tile I have two points being linked, one at the bottom of the tile and one on the right. It is up to the Train object to pick the correct order of the points based on the train’s direction of movement.

Ultimately the path object will need only one point per tile, once I filter results with direction. But more on this later.

And TARGET type has the possible values of NONE, END and BOMB. This means upon reaching one of these tiles the following will happen respectively: Nothing, the level will end successfully, the train will explode.

The Classes

For this stage of the logic I need only two classes. One for the individual Tile in the grid and one to form the grid and manage it.

Tile.as

package  {
 
 
 
	import flash.display.Sprite;
 
	import flash.geom.Point;
 
	import flash.geom.Rectangle;
 
 
 
	public class Tile  {
 
 
 
		public static const TILE_SIZE:int = 64;
 
 
 
		public static const TARGET_NONE:int = 0;
 
		public static const TARGET_BOMB:int = 1;
 
		public static const TARGET_END:int = 2;
 
 
 
		public static const TILE_EMPTY:int = 0;
 
		public static const TILE_HORIZONTAL:int = 1;
 
		public static const TILE_VERTICAL:int = 2;
 
		public static const TILE_TURN_UP_LEFT:int = 3;
 
		public static const TILE_TURN_UP_RIGHT:int = 4;
 
		public static const TILE_TURN_DOWN_LEFT:int = 5;
 
		public static const TILE_TURN_DOWN_RIGHT:int = 6;
 
		public static const TILE_CROSS_ROAD:int = 7;
 
 
 
		public var pointLeft:Point;
 
		public var pointRight:Point;
 
		public var pointUp:Point;
 
		public var pointDown:Point;
 
 
 
		public var skin:Sprite;
 
		public var types:Array;
 
 
 
		public var active:Boolean = false;		
 
		public var path_index:int;
 
		public var grid_index:int
 
		public var type:int;
 
		public var target:int;
 
		public var row:int;
 
		public var col:int;
 
		public var direction:int;
 
 
 
		private var _typeIndex:int = 0;
 
 
 
		public function Tile(index:int, row:int, col:int) {
 
 
 
			grid_index = index;
 
			this.row = row;
 
			this.col = col;
 
 
 
			skin = new Sprite();
 
			skin.graphics.beginFill(0x222222);
 
			skin.graphics.drawRect (-Tile.TILE_SIZE * 0.5, -Tile.TILE_SIZE * 0.5, Tile.TILE_SIZE, Tile.TILE_SIZE);
 
		}
 
 
 
		public function get bounds ():Rectangle {
 
			return new Rectangle (skin.x -Tile.TILE_SIZE * 0.5, skin.y -Tile.TILE_SIZE * 0.5, Tile.TILE_SIZE, Tile.TILE_SIZE);
 
		}
 
 
 
		public function turn ():void {
 
 
 
			_typeIndex++;
 
			if (_typeIndex >= types.length) _typeIndex = 0;
 
 
 
			type = types[_typeIndex];
 
 
 
			switch (type) {
 
				case Tile.TILE_HORIZONTAL:
 
					skin.rotation = 45;
 
				break;
 
				case Tile.TILE_VERTICAL:
 
					skin.rotation = 135;
 
				break;
 
				case Tile.TILE_TURN_UP_LEFT:
 
					skin.rotation = 0;
 
				break;
 
				case Tile.TILE_TURN_UP_RIGHT:
 
					skin.rotation = 90;
 
				break;
 
				case Tile.TILE_TURN_DOWN_LEFT:
 
					skin.rotation = -90;
 
				break;
 
				case Tile.TILE_TURN_DOWN_RIGHT:
 
					skin.rotation = 180;
 
				break;
 
			}
 
 
 
		}
 
 
 
		public function setData (x:Number, y:Number, data:Object):void {
 
 
 
			pointLeft = new Point(x - Tile.TILE_SIZE * 0.5, y);
 
			pointRight = new Point(x + Tile.TILE_SIZE * 0.5, y);
 
			pointUp = new Point(x, y - Tile.TILE_SIZE * 0.5);
 
			pointDown = new Point(x, y + Tile.TILE_SIZE * 0.5);
 
 
 
			types = data.types;
 
			type = types[0];
 
			target = data.target;
 
 
 
			if (type != Tile.TILE_EMPTY) {
 
 
 
				skin.graphics.beginFill(0x00FF00);
 
				skin.graphics.drawRect (-Tile.TILE_SIZE * 0.5, -Tile.TILE_SIZE * 0.5, Tile.TILE_SIZE, Tile.TILE_SIZE);
 
 
 
				if (types.length > 1) {
 
 
 
					skin = new TurnTile();
 
 
 
				   switch (type) {
 
						case Tile.TILE_HORIZONTAL:
 
							skin.rotation = 45;
 
						break;
 
						case Tile.TILE_VERTICAL:
 
							skin.rotation = 135;
 
						break;
 
						case Tile.TILE_TURN_UP_LEFT:
 
							skin.rotation = 0;
 
						break;
 
						case Tile.TILE_TURN_UP_RIGHT:
 
							skin.rotation = 90;
 
						break;
 
						case Tile.TILE_TURN_DOWN_LEFT:
 
							skin.rotation = -90;
 
						break;
 
						case Tile.TILE_TURN_DOWN_RIGHT:
 
							skin.rotation = 180;
 
						break;
 
					}
 
				} 
 
				active = true;
 
			}
 
 
 
			skin.x = x;
 
			skin.y = y;
 
		}
 
 
 
		public function trackIt (flag:Boolean):void {
 
 
 
			if (types.length > 1) return;
 
 
 
			if (flag) {
 
				skin.graphics.beginFill(0xFF0000);
 
				skin.graphics.drawRect (-Tile.TILE_SIZE * 0.5, -Tile.TILE_SIZE * 0.5, Tile.TILE_SIZE, Tile.TILE_SIZE);
 
			} else {
 
				skin.graphics.beginFill(0x00FF00);
 
				skin.graphics.drawRect (-Tile.TILE_SIZE * 0.5, -Tile.TILE_SIZE * 0.5, Tile.TILE_SIZE, Tile.TILE_SIZE);
 
			}
 
 
 
		}
 
	}
 
 
 
}

Main.as

package  {
 
 
 
	import flash.display.Sprite;
 
	import flash.geom.Point;
 
	import flash.events.Event;
 
	import flash.events.MouseEvent;
 
	import flash.utils.getTimer;
 
 
 
	[SWF (width="1024", height="768", backgroundColor="0x666666", frameRate="60")]
 
	public class Main extends Sprite {
 
 
 
		private var _tilesGrid:Array;
 
		private var _tilesTrack:Array;
 
		private var _path:Array;
 
		private var _pathPoints:Array;
 
		private var _container:Sprite;
 
		private var _dotsContainer:Sprite;
 
 
 
		private var _pathData:Array;
 
		private var _startTile:int;
 
		private var _trackingTile:int = 0;
 
 
 
		private var _rows:int;
 
		private var _cols:int;
 
 
 
		private var _train:Train;
 
		private var _bombTiles:Array;
 
		private var _endTile:Tile;
 
		private var _run:Boolean = false;
 
 
 
		public function Main() {
 
 
 
			//the entire grid (including all empty cells)
 
			_tilesGrid = [];
 
			//the track cells only
 
			_tilesTrack = [];
 
			//the track cells that contains bombs
 
			_bombTiles = [];
 
 
 
			//hard code path data
 
			initData();
 
 
 
			_container = new Sprite();
 
			addChild(_container);
 
 
 
			_dotsContainer = new Sprite();
 
			addChild(_dotsContainer);
 
 
 
			var w:int = stage.stageWidth;
 
			var h:int = stage.stageHeight;
 
 
 
			_rows = h / Tile.TILE_SIZE;
 
			_cols = w / Tile.TILE_SIZE;
 
 
 
			var tile:Tile;
 
			var dataIndex:int = 0;
 
 
 
			for (var i:int = 0; i < _rows; i++) {
 
				for (var j:int = 0; j < _cols; j++) {
 
					tile = new Tile(dataIndex, i, j);
 
					tile.setData(j * Tile.TILE_SIZE + Tile.TILE_SIZE * 0.5,  i * Tile.TILE_SIZE + Tile.TILE_SIZE * 0.5, _pathData[dataIndex]);
 
					_container.addChild (tile.skin);
 
 
 
					//store all grid cells
 
					_tilesGrid.push(tile);
 
 
 
					//populate array with track cells
 
					if (_pathData[dataIndex].types[0] != Tile.TILE_EMPTY) {
 
						if (_pathData[dataIndex].target == Tile.TARGET_END) {
 
							_endTile = tile;
 
						} else if (_pathData[dataIndex].target == Tile.TARGET_BOMB) {
 
							_bombTiles.push(tile);
 
						}
 
 
 
						_tilesTrack.push(tile);
 
					}
 
 
 
					dataIndex++;
 
				}
 
			}
 
 
 
			_startTile = _trackingTile = 66;
 
 
 
			_train = new Train(_container);
 
			_train.direction = Train.DIRECTION_RIGHT;
 
			_train.startDirection = Train.DIRECTION_RIGHT;
 
 
 
			trackPath();
 
 
 
			stage.addEventListener(MouseEvent.CLICK, onTrackClick, false, 0, true);
 
			addEventListener(Event.ENTER_FRAME, on_EnterFrame, false, 0, true);
 
 
 
			_run = true;
 
		}
 
 
 
		private var _timeThen:Number = 0;
 
		private var _timeNow:Number = 0;
 
 
 
		private function on_EnterFrame(event:Event):void {
 
 
 
			if (!_run) return;
 
 
 
			_timeNow = getTimer();
 
 
 
			var dt:Number;
 
			dt = (_timeNow - _timeThen) * 0.006;
 
 
 
			_train.update(dt);
 
 
 
			//check to see if train too close to a turn button (then make button inactive)
 
			var len:int = _tilesTrack.length;
 
			var tile:Tile;
 
			var i:int;
 
			for (i = 0; i < len; i++) {
 
				tile = _tilesTrack[i];
 
				if (!tile.active) continue;
 
 
 
				if (Point.distance(tile.origin, _train.getFirstCarPosition()) < Tile.TILE_SIZE) {
 
					tile.active = false;
 
					break;
 
				}
 
			}
 
 
 
			//check for arrival
 
			if (_train.arrived) {
 
				//check if near end tile
 
				if (Point.distance(_endTile.origin, _train.getFirstCarPosition()) < Tile.TILE_SIZE) {
 
					trace("YOU WON!!!!");
 
				} else {
 
					//check if near bomb tile
 
					var kaboom:Boolean = false;
 
					len = _bombTiles.length;
 
					for (i = 0; i < len; i++) {
 
						tile = _bombTiles[i];
 
						if (Point.distance(tile.origin, _train.getFirstCarPosition()) < Tile.TILE_SIZE) {
 
							kaboom = true;
 
							trace("KABOOOM!!!");
 
							break;
 
						}
 
					}
 
					//if none of above, player has failed during a turn
 
					if (!kaboom) trace("FAILED AT A TURN!");
 
				}
 
				_run = false;
 
			}
 
 
 
			_timeThen = _timeNow;
 
		}
 
 
 
		private function trackPath (index:int = 0):void {
 
 
 
			//clear current path
 
			var len:int = _tilesTrack.length;
 
			var i:int;
 
			for (i = 0; i < len; i++) {
 
				_tilesTrack[i].trackIt(false, 0, 0);
 
			}
 
 
 
			_tilesGrid[_startTile].trackIt(true, 0, _train.startDirection);
 
 
 
			_train.direction = _train.startDirection;
 
			_trackingTile = _startTile;
 
 
 
			var collect:Boolean = true;
 
			_path = [];
 
			_pathPoints = [];
 
 
 
			_path.push(_tilesGrid[_startTile]);
 
 
 
			while (collect) {
 
				collect = collectTile();
 
			}
 
 
 
			len = _dotsContainer.numChildren;
 
			for (i = len -1; i >= 0; i--) {
 
				_dotsContainer.removeChild(_dotsContainer.getChildAt(i));
 
			}
 
			len = _path.length;
 
			for (i = 0; i < len; i++) {
 
 
 
				if (_path[i].point) {
 
					drawDot(_path[i].point); 
 
					_pathPoints.push(_path[i].point);
 
 
 
					if (i == len - 1) {
 
						_pathPoints.push(_path[i].getExtraPoint());
 
						drawDot(_path[i].getExtraPoint());
 
					}
 
				}
 
 
 
			}
 
 
 
			_train.setTrack(_pathPoints);
 
		}
 
 
 
		private function collectTile ():Boolean {
 
 
 
			var current_tile:Tile = _tilesGrid[_trackingTile];
 
 
 
			var row:int = current_tile.row;
 
			var col:int = current_tile.col;
 
 
 
			var temp_tile:Tile;
 
 
 
			switch (_train.direction) {
 
 
 
				case Train.DIRECTION_RIGHT:
 
					_trackingTile += 1;
 
					if (_trackingTile >= _tilesGrid.length) _trackingTile = _tilesGrid.length - 1;
 
					temp_tile = _tilesGrid[_trackingTile];
 
 
 
					//if in the same row 
 
					if (temp_tile.row == current_tile.row) {
 
 
 
						//the possible correct options 
 
						if (temp_tile.type == Tile.TILE_HORIZONTAL ||
 
						temp_tile.type == Tile.TILE_TURN_UP_LEFT ||
 
						temp_tile.type == Tile.TILE_TURN_DOWN_LEFT || temp_tile.type == Tile.TILE_CROSS_ROAD) {
 
 
 
							temp_tile.trackIt(true, _path.length, _train.direction);
 
 
 
							_path.push(temp_tile);
 
 
 
							//change direction
 
							if (temp_tile.type == Tile.TILE_TURN_UP_LEFT) _train.direction = Train.DIRECTION_UP;
 
							if (temp_tile.type == Tile.TILE_TURN_DOWN_LEFT) _train.direction = Train.DIRECTION_DOWN;
 
 
 
							return true;
 
						}
 
 
 
					}
 
				break;
 
 
 
				case Train.DIRECTION_LEFT:
 
					_trackingTile -= 1;
 
					if (_trackingTile < 0) _trackingTile = 0;
 
					temp_tile = _tilesGrid[_trackingTile];
 
 
 
					//if in the same row 
 
					if (temp_tile.row == current_tile.row) {
 
 
 
						//the possible correct options 
 
						if (temp_tile.type == Tile.TILE_HORIZONTAL ||
 
						temp_tile.type == Tile.TILE_TURN_UP_RIGHT ||
 
						temp_tile.type == Tile.TILE_TURN_DOWN_RIGHT || temp_tile.type == Tile.TILE_CROSS_ROAD) {
 
 
 
							temp_tile.trackIt(true, _path.length, _train.direction);
 
							_path.push(temp_tile);
 
 
 
							//change direction
 
							if (temp_tile.type == Tile.TILE_TURN_UP_RIGHT) _train.direction = Train.DIRECTION_UP;
 
							if (temp_tile.type == Tile.TILE_TURN_DOWN_RIGHT) _train.direction = Train.DIRECTION_DOWN;
 
 
 
							return true;
 
						}
 
 
 
					}
 
				break;
 
 
 
				case Train.DIRECTION_UP:
 
					_trackingTile -= _cols;
 
					if (_trackingTile < 0) _trackingTile = 0;
 
					temp_tile = _tilesGrid[_trackingTile];
 
 
 
					if (temp_tile.row == current_tile.row - 1) {
 
 
 
						//the possible correct options 
 
						if (temp_tile.type == Tile.TILE_VERTICAL ||
 
						temp_tile.type == Tile.TILE_TURN_DOWN_LEFT ||
 
						temp_tile.type == Tile.TILE_TURN_DOWN_RIGHT || temp_tile.type == Tile.TILE_CROSS_ROAD) {
 
 
 
							temp_tile.trackIt(true, _path.length, _train.direction);
 
							_path.push(temp_tile);
 
 
 
							//change direction
 
							if (temp_tile.type == Tile.TILE_TURN_DOWN_LEFT) _train.direction = Train.DIRECTION_LEFT;
 
							if (temp_tile.type == Tile.TILE_TURN_DOWN_RIGHT) _train.direction = Train.DIRECTION_RIGHT;
 
 
 
							return true;
 
						}
 
 
 
					}
 
				break;
 
 
 
				case Train.DIRECTION_DOWN:
 
					_trackingTile += _cols;
 
					if (_trackingTile >= _tilesGrid.length) _trackingTile = _tilesGrid.length - 1;
 
					if (_trackingTile < 0) _trackingTile = 0;
 
					temp_tile = _tilesGrid[_trackingTile];
 
 
 
					if (temp_tile.row == current_tile.row + 1) {
 
 
 
						//the possible correct options 
 
						if (temp_tile.type == Tile.TILE_VERTICAL ||
 
						temp_tile.type == Tile.TILE_TURN_UP_LEFT ||
 
						temp_tile.type == Tile.TILE_TURN_UP_RIGHT || temp_tile.type == Tile.TILE_CROSS_ROAD) {
 
 
 
							temp_tile.trackIt(true, _path.length, _train.direction);
 
							_path.push(temp_tile);
 
 
 
							//change direction
 
							if (temp_tile.type == Tile.TILE_TURN_UP_LEFT) _train.direction = Train.DIRECTION_LEFT;
 
							if (temp_tile.type == Tile.TILE_TURN_UP_RIGHT) _train.direction = Train.DIRECTION_RIGHT;
 
 
 
							return true;
 
						}
 
 
 
					}
 
				break;
 
			}
 
 
 
			return false;
 
		}
 
 
 
		private function onTrackClick (event:MouseEvent):void {
 
			var len:int = _tilesTrack.length;
 
			var tile:Tile;
 
 
 
			for (var i:int = 0; i < len; i++) {
 
				tile = _tilesTrack[i];
 
 
 
				if (!tile.active) continue;
 
 
 
				//track mouse click
 
				if (tile.bounds.contains(stage.mouseX, stage.mouseY)) {
 
					if (tile.types.length > 1) {
 
						_run = false;
 
						tile.turn();
 
						trackPath(tile.path_index);
 
						_run = true;
 
					}
 
					break;
 
				}
 
			}
 
 
 
		}
 
 
 
		private function initData ():void {
 
 
 
			_pathData = [
 
 
 
			//ROW 1
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			//ROW 2
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_BOMB},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			//ROW 3
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			//ROW 4
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			//ROW 5
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_TURN_UP_LEFT, Tile.TILE_VERTICAL, Tile.TILE_TURN_DOWN_LEFT, Tile.TILE_HORIZONTAL, Tile.TILE_TURN_DOWN_RIGHT, Tile.TILE_VERTICAL, Tile.TILE_TURN_UP_RIGHT, Tile.TILE_HORIZONTAL], target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_TURN_DOWN_LEFT],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			//ROW 6
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			//ROW 7
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			//ROW 8
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_END},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_CROSS_ROAD], target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_HORIZONTAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL, Tile.TILE_TURN_DOWN_RIGHT, Tile.TILE_HORIZONTAL, Tile.TILE_TURN_DOWN_LEFT, Tile.TILE_TURN_UP_LEFT], target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			//ROW 9
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			//ROW 10
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			//ROW 11
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_BOMB},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_VERTICAL],  target: Tile.TARGET_BOMB},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			//ROW 12
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
			{types:[Tile.TILE_EMPTY],  target: Tile.TARGET_NONE},
 
 
 
			];
 
		}
 
 
 
		private function drawDot (point:Point):void {
 
 
 
			if (!point) return;
 
 
 
			var dot:Sprite = new Sprite();
 
			dot.graphics.beginFill(0xFF9900);
 
			dot.graphics.drawCircle(point.x, point.y, 5);
 
			_dotsContainer.addChild(dot);
 
		}
 
	}
 
}

There’s still a lot of test code in here, like the logic to add dots representing the path. But the main part of the logic happens in the methods trackPath and collectTile inside the document class.

The first method uses a while loop to form the track. In a more complex track, or a larger grid it might be better to use a recurrent method to collect the tracks.

TrackPath method

private function trackPath (index:int = 0):void {
 
 
 
	//clear current path
 
	var len:int = _tilesTrack.length;
 
	var i:int;
 
	for (i = 0; i < len; i++) {
 
		_tilesTrack[i].trackIt(false, 0, 0);
 
	}
 
 
 
	_tilesGrid[_startTile].trackIt(true, 0, _train.startDirection);
 
 
 
	_train.direction = _train.startDirection;
 
	_trackingTile = _startTile;
 
 
 
	var collect:Boolean = true;
 
	_path = [];
 
	_pathPoints = [];
 
 
 
	_path.push(_tilesGrid[_startTile]);
 
 
 
	while (collect) {
 
		collect = collectTile();
 
	}
 
 
 
	//test code to add dots
 
	len = _dotsContainer.numChildren;
 
	for (i = len -1; i >= 0; i--) {
 
		_dotsContainer.removeChild(_dotsContainer.getChildAt(i));
 
	}
 
	len = _path.length;
 
	for (i = 0; i < len; i++) {
 
 
 
		if (_path[i].point) {
 
			drawDot(_path[i].point); 
 
			_pathPoints.push(_path[i].point);
 
 
 
			if (i == len - 1) {
 
				_pathPoints.push(_path[i].getExtraPoint());
 
				drawDot(_path[i].getExtraPoint());
 
			}
 
		}
 
	}
 
	_train.setTrack(_pathPoints);
 
}

CollectTile method

private function collectTile ():Boolean {
 
	var current_tile:Tile = _tilesGrid[_trackingTile];
 
 
 
	var row:int = current_tile.row;
 
	var col:int = current_tile.col;
 
 
 
	var temp_tile:Tile;
 
 
 
	switch (_train.direction) {
 
 
 
		case Train.DIRECTION_RIGHT:
 
			_trackingTile += 1;
 
			if (_trackingTile >= _tilesGrid.length) _trackingTile = _tilesGrid.length - 1;
 
			temp_tile = _tilesGrid[_trackingTile];
 
 
 
			//if in the same row 
 
			if (temp_tile.row == current_tile.row) {
 
 
 
				//the possible correct options
 
				if (temp_tile.type == Tile.TILE_HORIZONTAL ||
 
				temp_tile.type == Tile.TILE_TURN_UP_LEFT ||
 
				temp_tile.type == Tile.TILE_TURN_DOWN_LEFT || temp_tile.type == Tile.TILE_CROSS_ROAD) {
 
 
 
					temp_tile.trackIt(true, _path.length, _train.direction);
 
 
 
					_path.push(temp_tile);
 
 
 
					//change direction
 
					if (temp_tile.type == Tile.TILE_TURN_UP_LEFT) _train.direction = Train.DIRECTION_UP;
 
					if (temp_tile.type == Tile.TILE_TURN_DOWN_LEFT) _train.direction = Train.DIRECTION_DOWN;
 
 
 
					return true;
 
				}
 
 
 
			}
 
		break;
 
 
 
		case Train.DIRECTION_LEFT:
 
			_trackingTile -= 1;
 
			if (_trackingTile < 0) _trackingTile = 0;
 
			temp_tile = _tilesGrid[_trackingTile];
 
 
 
			//if in the same row 
 
			if (temp_tile.row == current_tile.row) {
 
 
 
				//the possible correct options
 
				if (temp_tile.type == Tile.TILE_HORIZONTAL ||
 
				temp_tile.type == Tile.TILE_TURN_UP_RIGHT ||
 
				temp_tile.type == Tile.TILE_TURN_DOWN_RIGHT || temp_tile.type == Tile.TILE_CROSS_ROAD) {
 
 
 
					temp_tile.trackIt(true, _path.length, _train.direction);
 
					_path.push(temp_tile);
 
 
 
					//change direction
 
					if (temp_tile.type == Tile.TILE_TURN_UP_RIGHT) _train.direction = Train.DIRECTION_UP;
 
					if (temp_tile.type == Tile.TILE_TURN_DOWN_RIGHT) _train.direction = Train.DIRECTION_DOWN;
 
 
 
					return true;
 
				}
 
 
 
			}
 
		break;
 
 
 
		case Train.DIRECTION_UP:
 
			_trackingTile -= _cols;
 
			if (_trackingTile < 0) _trackingTile = 0;
 
			temp_tile = _tilesGrid[_trackingTile];
 
 
 
			if (temp_tile.row == current_tile.row - 1) {
 
 
 
				//the possible correct options
 
				if (temp_tile.type == Tile.TILE_VERTICAL ||
 
				temp_tile.type == Tile.TILE_TURN_DOWN_LEFT ||
 
				temp_tile.type == Tile.TILE_TURN_DOWN_RIGHT || temp_tile.type == Tile.TILE_CROSS_ROAD) {
 
 
 
					temp_tile.trackIt(true, _path.length, _train.direction);
 
					_path.push(temp_tile);
 
 
 
					//change direction
 
					if (temp_tile.type == Tile.TILE_TURN_DOWN_LEFT) _train.direction = Train.DIRECTION_LEFT;
 
					if (temp_tile.type == Tile.TILE_TURN_DOWN_RIGHT) _train.direction = Train.DIRECTION_RIGHT;
 
 
 
					return true;
 
				}
 
 
 
			}
 
		break;
 
 
 
		case Train.DIRECTION_DOWN:
 
			_trackingTile += _cols;
 
			if (_trackingTile >= _tilesGrid.length) _trackingTile = _tilesGrid.length - 1;
 
			if (_trackingTile < 0) _trackingTile = 0;
 
			temp_tile = _tilesGrid[_trackingTile];
 
 
 
			if (temp_tile.row == current_tile.row + 1) {
 
 
 
				//the possible correct options
 
				if (temp_tile.type == Tile.TILE_VERTICAL ||
 
				temp_tile.type == Tile.TILE_TURN_UP_LEFT ||
 
				temp_tile.type == Tile.TILE_TURN_UP_RIGHT || temp_tile.type == Tile.TILE_CROSS_ROAD) {
 
 
 
					temp_tile.trackIt(true, _path.length, _train.direction);
 
					_path.push(temp_tile);
 
 
 
					//change direction
 
					if (temp_tile.type == Tile.TILE_TURN_UP_LEFT) _train.direction = Train.DIRECTION_LEFT;
 
					if (temp_tile.type == Tile.TILE_TURN_UP_RIGHT) _train.direction = Train.DIRECTION_RIGHT;
 
 
 
					return true;
 
				}
 
 
 
			}
 
		break;
 
	}
 
 
 
	return false;
 
}

So the _path array stores the tiles used in the formed path.

Although it looks complicated, the logic is pretty simple. It checks on the Train’s direction of movement and then picks the next tile object in the grid based on that direction. Then it checks on the type of tile to determine if the track has ended or if it will cause the train change direction in the next check.

NOTE: Right now each time the player changes the path by clicking on a turn button, I get rid of all the current path and start collecting tiles from the start point again. But I have added logic to allow for an optimized version of this, so that the path needs only to be changed starting from the point where the player has made the change. All you need to accomplish that is the index inside the current path array in which the change starts (in other words the _path index of the turn button the player has clicked on), and the direction the train was moving at when it reached that turn tile. Both things are stored inside the Tile objects.

So here is the demo with the logic for track formation:

Click on the Turn Buttons to change the Path