import com.baseoneonline.haxe.astar.AStar; import com.baseoneonline.haxe.astar.AStarNode; import com.baseoneonline.haxe.geom.IntPoint; import com.baseoneonline.haxe.utils.StopWatch; import flash.Lib; import flash.display.Stage; import flash.display.Sprite; import flash.display.Shape; import flash.display.StageScaleMode; import flash.display.StageAlign; import flash.display.Graphics; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Point; import src.MapView; import src.TileMap; class AStarHaxe { // The example map and it's view // could be one class as well. private var map:TileMap; private var mapView:MapView; // Where the path will be drawn private var overlay:Sprite; // Size in pixels per tile private var tileSize:Int; // Show where we click private var startCursor:Shape; private var goalCursor:Shape; // Our start and goal positions private var start:IntPoint; private var goal:IntPoint; // Not very precise measuring tool private var stopwatch:StopWatch; public var stage:Stage; public static function main() { new AStarHaxe(); } private function new(){ stage = Lib.current.stage; tileSize = 18; stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; //addEventListener(Event.ADDED_TO_STAGE, init); init(); } private function init():Void { // First create a map to work with, // This can be any kind of 2D tilemap as // long as it implements IAStarSearchable map = new TileMap(32,32); // Make a random tiles unwalkable placeRandomWalls(map,.01+Math.random()*.4); // Just a simple grid view mapView = new MapView(map, tileSize); stage.addChild(mapView); mapView.draw(); // Add the overlay to draw on overlay = new Sprite(); stage.addChild(overlay); // Create the cursors startCursor = createCursor(tileSize-2, 0x0000FF); stage.addChild(startCursor); goalCursor = createCursor(tileSize-3, 0xFF0000); stage.addChild(goalCursor); // A stopwatch stopwatch = new StopWatch("Last solve time:"); stage.addChild(stopwatch); stopwatch.y = 580; // No fun without this stage.addEventListener(MouseEvent.CLICK, onClick); } /** * Make a square to mark a position. * */ private function createCursor(size:Int, col:UInt):Shape { var s:Shape = new Shape(); var g:Graphics = s.graphics; g.lineStyle(3,col); g.drawRect(-size/2,-size/2, size, size); return s; } /** * Place the cursors on their positions. * */ private function updateCursors():Void { var p:Point; if (start!=null) { p = mapToScreen(start); startCursor.x = p.x; startCursor.y = p.y; } if (goal!=null) { p = mapToScreen(goal); goalCursor.x = p.x; goalCursor.y = p.y; } } /** * Return a random position on the map that is walkable * */ private function getRandomWalkablePos(map:TileMap):IntPoint { var x:Int; var y:Int; while(true) { x = Std.int(Math.random()*map.getWidth()); y = Std.int(Math.random()*map.getHeight()); //trace(map.isWalkable(x,y)); if (map.isWalkable(x,y)) return new IntPoint(x,y); } return null; } /** * On which tile is a point on the screen? * */ private function screenToMap(p:Point):IntPoint { return new IntPoint( Math.floor(p.x/tileSize), Math.floor(p.y/tileSize) ); } /** * Return the center position of a tile in pixels * */ private function mapToScreen(p:IntPoint):Point { return new Point( (p.x*tileSize)+(tileSize/2), (p.y*tileSize)+(tileSize/2) ); } /** * Handle click, start solving * * */ private function onClick(e:MouseEvent):Void { // Clear, or we have rubble next time we click overlay.graphics.clear(); // Pick a random position on the map the first time. if (goal==null) goal = getRandomWalkablePos(map); // Shift: arrival point is now starting point start = goal; // Mark the clicked position as the goal goal = screenToMap(new Point(stage.mouseX, stage.mouseY)); // Give feedback updateCursors(); // Only calculate if the goal is not a wall if (map.isWalkable(goal.x,goal.y)) { // Start measuring the time // it will take to calculate a path stopwatch.start(); // Say, little star, show me the way... // var a:AStar = new AStar(map, start, goal); var solution:Array = a.solve(); // How long did it take? stopwatch.display(); // Draw all visited nodes (just for fun) var j:Int = 0; for (j in 0...a.visited.length) { var vn:AStarNode = a.visited[j]; drawHalfLine(vn, vn.parent, 0xEE4557); drawCircle(vn, 0xDD4557); } // Do we have a path? if (solution!=null) { // YES! Loop and plot... var n:IntPoint = solution[0]; var i:Int = 1; for (i in 0...solution.length) { var n2:IntPoint = n; n = solution[i]; drawLine(n,n2,0x000000, 3); } drawLine(n,start,0x000000,3); } } else { // Alas, just plot a line drawLine(start, goal, 0x0000FF); stopwatch.reset(); } } /** * Draw a line from a to b * */ private function drawLine(a:IntPoint, b:IntPoint, col:UInt, width:Int=1):Void { var pa:Point = mapToScreen(a); var pb:Point = mapToScreen(b); overlay.graphics.lineStyle(width,col); overlay.graphics.moveTo(pa.x, pa.y); overlay.graphics.lineTo(pb.x, pb.y); } private function drawHalfLine(a:IntPoint, b:IntPoint, col:UInt, width:Int=1):Void { var pa:Point = mapToScreen(a); var pb:Point = mapToScreen(b); pb.x = (pa.x + pb.x) / 2; pb.y = (pa.y + pb.y) / 2; overlay.graphics.lineStyle(width,col); overlay.graphics.moveTo(pa.x, pa.y); overlay.graphics.lineTo(pb.x, pb.y); } /** * Draw a circle for funsies * */ private function drawCircle(b:IntPoint, col:UInt):Void { var pb:Point = mapToScreen(b); overlay.graphics.lineStyle(3,col); overlay.graphics.drawCircle(pb.x, pb.y, 2); } /** * Place random blocking tiles on the map */ private static function placeRandomWalls(map:TileMap, bias:Float=.1):Void { var x:Int = 0; var y:Int = 0; for (x in 0...map.getWidth()) { for (y in 0...map.getHeight()) { map.setWalkable(x,y,Math.random() > bias); } } } }