I have code that is supposed to find the shortest path from point A to point B. To do this i am using a A-star variation. I am using a 2d array to represent a 2d grid but my path does not take diagonal shortcuts, only left, right, up, and down. So far everything works fine except it does not always find the shortest path possible. I want to know what is going wrong, why it is going wrong, and how I can fix it. Thank you in advance.
Here is a picture to illustrate what exactly is happening:
and here is my code (path finding class first, then its helper class):
BTW: Math vector is nothing more than just a geometric point class, and both playerTileLocation and enemyTileLocation are just points that correspond to the start and end nodes on the grid. Also i use the class AStarNode as the nodes for all the tiles on the map, instead of a regular object.
package {
import src.Characters.Character;
import src.InGame.Map;
import src.Maths.MathVector;
public final class BaseAI {
// REPRESENTS UP, DOWN, RIGHT, AND LEFT OF ANY ONE NODE
private static const bordersOfNode:Array = new Array(
new MathVector( -1, 0), new MathVector(1, 0), new MathVector(0, -1), new MathVector(0, 1));
private var _player:Character;
private var map:Map;
private var playerTileLocation:MathVector;
private var openList:Array;
private var closedList:Array;
// 2D ARRAY OF MAP TILES (I DON'T USE HERE, BUT I PLAN TO IN FUTURE)
private var mapArray:Array;
private var originNode:AStarNode;
private var complete:Boolean;
public function BaseAI(_player:Character,map:Map):void {
this._player = _player;
this.map = map;
openList = new Array();
closedList = new Array();
mapArray = map.tiles;
}
public function get player():Character {
return this._player;
}
public function calculatePlayerTileLocation():void {
playerTileLocation = map.worldToTilePoint(player.groundPosition);
}
//WILL EVENTUAL RETURN A DIRECTION FOR THE ENEMY TO TAKE THAT ITERATION (EVERY 1-2 SECONDS)
public function getDirection(enemy:Character):String {
var enemyTileLocation:MathVector = map.worldToTilePoint(enemy.groundPosition);
originNode = new AStarNode(enemyTileLocation, playerTileLocation);
originNode.setAsOrigin();
openList = [originNode];
closedList = [];
complete = false;
var currentNode:AStarNode;
var examiningNode:AStarNode;
while (!complete) {
openList.sortOn("F", Array.NUMERIC);
currentNode = openList[0];
closedList.push(currentNode);
openList.splice(0, 1);
for (var i in bordersOfNode) {
examiningNode = new AStarNode(new MathVector(currentNode.X + bordersOfNode[i].x, currentNode.Y + bordersOfNode[i].y),playerTileLocation);
if (map.isOpenTile(map.getTile(examiningNode.X, examiningNode.Y)) && !examiningNode.isThisInArray(closedList)) {
if (!examiningNode.isThisInArray(openList)) {
openList.push(examiningNode);
examiningNode.parentNode = currentNode;
}else {
}
if (examiningNode.X == playerTileLocation.x && examiningNode.Y == playerTileLocation.y) {
complete = true;
var done:Boolean = false;
var thisNode:AStarNode;
thisNode = examiningNode;
while (!done) {
if (thisNode.checkIfOrigin()) {
done = true;
}else {
thisNode = thisNode.parentNode;
}
}
}
}
}
}
}
}
}
package {
import src.Maths.MathVector;
internal final class AStarNode {
private var _X:int;
private var _Y:int;
private var _G:int;
private var _H:int;
private var _F:int;
private var _parentNode:AStarNode;
private var _isOrigin:Boolean;
public static const VERTICAL:uint = 10;
public function AStarNode(thisNodeLocation:MathVector, targetNodeLocation:MathVector) {
X = thisNodeLocation.x;
Y = thisNodeLocation.y;
H = Math.abs(X - targetNodeLocation.x) + Math.abs(Y - targetNodeLocation.y);
G = 0;
F = H + G;
}
public function set X(newX:int):void {
this._X = newX;
}
public function get X():int {
return this._X;
}
public function set Y(newY:int):void {
this._Y = newY;
}
public function get Y():int {
return this._Y;
}
public function set G(newG:int):void {
this._G = newG;
}
public function get G():int {
return this._G;
}
public function set H(newH:int):void {
this._H = newH;
}
public function get H():int {
return this._H;
}
public function set F(newF:int):void {
this._F = newF;
}
public function get F():int {
return this._F;
}
public function set parentNode(newParentNode:AStarNode):void {
this._parentNode = newParentNode;
}
public function get parentNode():AStarNode {
return this._parentNode;
}
public function setAsOrigin():void {
_isOrigin = true;
}
public function checkIfOrigin():Boolean {
return _isOrigin;
}
public function isThisInArray(arrayToCheck:Array):Boolean {
for (var i in arrayToCheck) {
if (arrayToCheck[i].X == this.X && arrayToCheck[i].Y == this.Y) {
return true
}
}
return false
}
}
enter code here
}
A quick glance through your code raises the idea of wrong heuristics. Your G value is always 0 in a node, at lease I do not see where it could change. However, in A-star algorithm for your task (finding the shortest path with obstacles) it should represent the number of steps already made to reach the cell. That would allow the algorithm to replace the long path with a shorter one.
The one time I coded an A star 'algorithm' I used a 2-dimensional Array for the grid (as you have). At the start of the search each grid location's 'searched' property was set to false. Each grid location would also have an Array of connecting directions; options that the player could choose to move in - some might be open, some might be blocked and inaccessible.
I would start the search by checking the starting grid position for how many direction options it had. For each option I would push a 'path' Array into a _paths Array. Each 'path' Array would end up containing a sequence of 'moves' (0 for up, 1 for right, 2 for down and 3 for left). So for each initial path, I would push in the corresponding starting move. I would also set the grid position's 'searched' property to true.
I would then iterate through each path, running through that sequence of moves to get to the most recently added location. I would check if that location was the target location. If not I would mark that location as searched then check which directions were available, ignoring locations that had already been searched. If non were available, the path would be closed and 'spliced' from the Array of paths.
Otherwise ByteArray 'deep copies' of the current path Array were made for each available move option, in excess of the first move option. A move in one direction was added to the current path and the new paths, in their respective directions.
If the number of paths ever reaches 0, there is not a path between the 2 locations.
I think that was about it. I hope that's helpful.
Note that the search does not need to be 'directed' toward the target; what I've suggested searches all possible paths and just 'happens' to find the most direct route by killing paths that try to check locations that have already been searched (meaning some other path has got there first and is therefore shorter).
Related
Ok this has been driving me insane. My AS3 knowledge isn't the best in the world, but I'm trying to work out where I'm going wrong with all of this.
Basically, What I'm trying to do is at certain times, make visible/invisble two different MovieClips.
The weird thing is, one is responding. And the other isn't. They are both identical aside from jpeg contents and names. Is there a setting I'm missing? Both have matched MovieClip names and Instance names... but when I use the code below, HOP1 turns off/on, but HOP2 refuses to! Am i just missing some stupidly obvious preference?
I will mention, I'll have to modify the code to work with two different MovieClips, but right now I just want both files to turn off!
package {
import flash.display.MovieClip;
import flash.events.TimerEvent;
import flash.ui.Mouse;
import flash.utils.Timer;
import com.boo.CustomDate;
import com.boo.ScreensaverSimple;
public class Generic extends MovieClip {
// This is where you can set the Hour of Power time start and end time (in 24 hour format e.g. 1330 for 1:30pm)
// If there is no hour of power, simply set both numbers to 0
private var HourOfPowerStartTime:Number = 0;
private var HourOfPowerEndTime:Number = 0;
private var ss:ScreensaverSimple;
public var time_check_timer:Timer;
private var delay_add_timer:Timer;
public function Generic() {
Mouse.hide();
ss = new ScreensaverSimple;
ss.setScreensaver(screens);
HOP2.visible = false;
time_check_timer = new Timer(1000);
time_check_timer.addEventListener(TimerEvent.TIMER, checkTime);
delay_add_timer = new Timer(1,1);
delay_add_timer.addEventListener(TimerEvent.TIMER, addAllChildren);
delay_add_timer.start();
}
public function addAllChildren(evt:TimerEvent=null):void {
delay_add_timer.removeEventListener(TimerEvent.TIMER, addAllChildren);
delay_add_timer.stop();
delay_add_timer = null;
time_check_timer.start();
checkTime();
}
public function checkTime(evt:TimerEvent=null):void {
checkHOP2();
}
private function checkHOP1():void {
if(HourOfPowerStartTime == 0 && HourOfPowerEndTime == 0)
{
if(HOP2.visible == true)
{
HOP2.visible = false;
}
return;
}
var CurrentTime:Number = CustomDate.return24HourNumber();
if(CurrentTime >= HourOfPowerStartTime && CurrentTime <= HourOfPowerEndTime)
{
if(HOP2.visible == false)
{
HOP2.visible = true;
}
}
else
{
if(HOP2.visible == true)
{
HOP2.visible = false;
}
}
}
}
}
if(HOP2.visible == true)
{
HOP2.visible = false;
}
Fist thing the if condition is complete redundant here. If you think about it, those lines work exactly the same as this one alone:
HOP2.visible = false;
Also (HOP2.visible == true) would be exactly the same as (HOP2.visible) and also you can assign value of condition check directly to variable. Generally you can reduce your function to:
private function checkHOP1():void {
HOP2.visible = (HourOfPowerStartTime || HourOfPowerEndTime);
if (!HOP2.visible) return;
var CurrentTime:Number = CustomDate.return24HourNumber();
HOP2.visible = (CurrentTime >= HourOfPowerStartTime && CurrentTime <= HourOfPowerEndTime);
}
Then I see you call to checkHOP2() :
public function checkTime(evt:TimerEvent=null):void {
checkHOP2();
}
but I don't see the checkHOP2() function defined in code you gave.
Similarly I don't see form where you call your checkHOP1() function you have posted. And also I don't get why change HOP2 instance inside function named checkHOP1() . Is it suppose to be some kind of obfuscation?
public class Hangman extends Sprite {
private var textDisplay:TextField;
private var phrase:String = "Recycled"
private var phrase:String = "Stamped"
private var phrase:String = "grandpa"
"What I want to do here is to randomise the "phrase:String", so that the phrase outcome will be either recycled, stamped or grandpa.
private var shown:String;
private var numWrong:int;
public function Hangman() {
// create a copy of text with _ for each letter
shown = phrase.replace(/[A-Za-z]/g,"_");
numWrong = 0;
...codes*
}
public function pressKey(event:KeyboardEvent) {
// get letter pressed
var charPressed:String = (String.fromCharCode(event.charCode));
// loop through nd find matching letters
var foundLetter:Boolean = false;
for(var i:int=0;i<phrase.length;i++) {
if (phrase.charAt(i).toLowerCase() == charPressed) {
// match found, change shown phrase
shown = shown.substr(0,i)+phrase.substr(i,1)+shown.substr(i+1);
foundLetter = true;
}
}
// update on-screen text
textDisplay.text = shown;
// update hangman
if (!foundLetter) {
numWrong++;
character.gotoAndStop(numWrong+1);
}
}
}
}
I hope someone can help me on this one. Thank you.
You cannot have the same variable being instantiated with the same name... if you want, use an array to keep the possible words...
var phrase:Array = [ "Recycled", "Stamped", "grandpa", ...];
Then, use a Random function to select a number from 0, up to array size, then use that word...
var word = phrase[Math.floor(Math.random()*phrase.length)];
Here is the problem, the object is moved together with the clicked object. I want it to be moveable following the mouse pointer, but let the clicked object stays. so when an object is clicked, there will be 2 objects in the stage(the static and moving one).
I think I've figured it out by adding a new object to be moved. in function onClickHero I've tried movingHero = new heroes but it says "call to a possibly undefined method heroes". My question is there any other way how to make another clone of the clicked object since I made it in array? And why does movingHero = new heroes doesn't work?
I'm still amateur at classes. Sorry if it's messed up. Thanks for helping.
package {
import flash.display.MovieClip
import flash.events.MouseEvent
import flash.events.Event
import flash.display.Sprite
public class Hero {
private var heroesArray:Array;
private var heroContainer:Sprite = new Sprite;
private var hero1:MovieClip = new Hero1();
private var hero2:MovieClip = new Hero2();
private var moveHero:Boolean = false;
private var movingHero:MovieClip;
private var _money:Money = new Money();
private var _main:Main;
public function Hero(main:Main)
{ _main = main;
heroesArray = [hero1,hero2];
heroesArray.forEach(addHero);
}
public function addHero(heroes:MovieClip,index:int,array:Array):void
{
heroes.addEventListener(Event.ENTER_FRAME, playerMoving);
heroes.addEventListener(MouseEvent.CLICK, chooseHero);
}
public function playerMoving(e:Event):void
{
if (moveHero == true)
{
movingHero.x = _main.mouseX;
movingHero.y = _main.mouseY;
}
}
public function chooseHero(e:MouseEvent):void
{
var heroClicked:MovieClip = e.currentTarget as MovieClip;
var cost:int = _main._money.money ;
if(cost >= 10 && moveHero == false)
{
_main._money.money -= 10;
_main._money.addText(_main);
onClickHero(heroClicked);
moveHero = true;
}
}
public function onClickHero(heroes:MovieClip):void
{
movingHero = heroes;
heroContainer.addChild(movingHero);
}
public function displayHero(stage:Object):void
{
stage.addChild(heroContainer);
for (var i:int = 0; i<2;i++)
{
stage.addChild(heroesArray[i]);
heroesArray[i].x = 37;
heroesArray[i].y = 80+i*70;
heroesArray[i].width=60;
heroesArray[i].height=55;
heroesArray[i].buttonMode = true;
}
}
}
}
EDIT: I've tried to make movingHero = new Hero1(); but since I don't know which hero will be clicked so I can't just use Hero1 from library. and If I use movingHero = heroClicked I only get the value of hero1 which is a var from Hero1 movieclip. So, is there any way to call the movie clip from library the same as which hero was clicked in stage?
You seemingly want to clone an object while not knowing its type. If that object also containg game logic, it's not the best idea to say spawn new heroes of either type, this might make a mess of your code. But if not, you can get the exact class of the object given, and make an object of that class via the following code:
public function onClickHero(heroes:MovieClip):void
{
if (!heroes) {
trace('heroes is null!');
return;
}
var heroClass:Class = getDefinitionByName(getQualifiedClassName(heroes)) as Class;
movingHero = new heroClass(); // instantiate that class
heroContainer.addChild(movingHero);
// movingHero.startDrag(); if needed
}
Don't forget to clean up the movingHero once it's no longer needed.
I would like to implement a very simple way to store a variable containing the last specific "CustomObject" I clicked. I'd like clicks on other objects to be ignored. Take the following sample code for example, given CustomObject extends MovieClip:
//Code within the Document Class:
var square1:CustomObject = new CustomObject();
var square2:CustomObject = new CustomObject();
var square3:CustomObject = new CustomObject();
var triangle1:DifferentObject= new DifferentObject();
square1.x=100; square2.x=200; square3.x=300;
addChild(square1);
addChild(square2);
addChild(square3);
addChild(triangle1);
//Code within the CustomObject Class:
this.addEventListener(MouseEvent.CLICK,radioButtonGlow);
public function radioButtonGlow(e:MouseEvent):void
{
var myGlow:GlowFilter = new GlowFilter();
myGlow.color = 0xFF0000;
myGlow.blurX = 25;
myGlow.blurY = 25;
this.filters = [myGlow];
}
This works great for whenever I click on squares- they light up exactly as expected. However, I'd like to implement a functionality that:
1) Stores the last square I clicked into a variable in the document class
2) Removes the glow from all other squares when I click on another one
Any feedback is greatly appreciated!
I suggest creating a class that acts as a collection of CustomObject instances and manages them in that manner (i.e. ensuring only one of that collection can be selected, etc).
Sample:
public class CustomCollection
{
// Properties.
private var _selected:CustomObject;
private var _items:Array = [];
// Filters.
private const GLOW:GlowFilter = new GlowFilter(0xFF0000, 25, 25);
// Constructor.
// #param amt The amount of CustomObjects that should belong to this collection.
// #param container The container to add the CustomObjects to.
public function CustomCollection(amt:int, container:Sprite)
{
for(var i:int = 0; i < amt; i++)
{
var rb:CustomObject = new CustomObject();
rb.x = i * 100;
_items.push(rb);
container.addChild(rb);
}
}
// Selects a CustomObject at the specified index.
// #param index The index of the CustomObject to select.
public function select(index:int):void
{
for(var i:int = 0; i < _items.length; i++)
{
if(i == index)
{
_selected = _items[i];
_selected.filters = [GLOW];
continue;
}
_items[i].filters = [];
}
}
// The currently selected CustomObject.
public function get selected():CustomObject
{
return _selected;
}
// A copy of the array of CustomObjects associated with this collection.
public function get items():Array
{
return _items.slice();
}
}
Then you can revise your code in the document class to something like:
var collection:CustomCollection = new CustomCollection(3, this);
collection.select(1);
You will need to add your own logic for the click event that deals with selecting the buttons. I suggest adding an index property to each CustomObject as well as a reference to the collection it was added to. That way, you can simply add the click event into the CustomObject class and have the handler function something like:
private function _click(e:MouseEvent):void
{
_collection.select(index);
}
I know that AS3 does not have pointer or reference. Every object is pass by reference already. (I supposed?)
What should I do if I want to do with pointer?
e.g. all object point to one target, I only need to change target's value, then all other object will access different value.
You can effectively get the same behavior by using a helper object to simulate a pointer -- in other words using it to carry the target reference. For instance:
public class PseudoPointer
{
private var obj:Object;
private var prop:String;
public function PseudoPointer(obj:Object, prop:String)
{
// Point to property with name 'prop' on object 'obj'.
this.obj = obj;
this.prop = prop;
}
public function get value():* {
return obj[prop];
}
public function set value(value:*):void {
obj[prop] = value;
}
}
Then you could do this -- assume there's a magicNumber property on an object named target:
var numberPtr = new PseudoPointer(target, "magicNumber");
myDynamicObjectA.numberPtr = numberPtr;
myDynamicObjectB.numberPtr = numberPtr;
myDynamicObjectC.numberPtr = numberPtr;
Now any object that has a reference to the pseudo-pointer can read and write the target property:
numberPtr.value = 42;
You could create a function and in which you give it the value and then subsequently assign it to all of those other variables.
Something like below:
var objectA:Number;
var objectB:Number;
...
function myFunction(newValue:Number):void
{
objectA = newValue;
objectB = newValue;
...
}
You could try setting a variable reference to a function. Then if you update that reference, it would return a different function.
var myFunc:Function;
function funcOne():int {
return 1;
}
function funcTwo():int {
return 2;
}
function getFunc():Function {
return myFunc;
}
myFunc = funcOne;
var myObjOne:Object = new Object();
myObjOne.objFunc = getFunc;
var myObjTwo:Object = new Object();
myObjTwo.objFunc = getFunc;
trace(myObjOne.objFunc.call().call()); // 1
trace(myObjTwo.objFunc.call().call()); // 1
myFunc = funcTwo;
trace(myObjOne.objFunc.call().call()); // 2
trace(myObjTwo.objFunc.call().call()); // 2
This allows any object to point at a single function and have what that returns be updateable for all of them simultaneously. It's not the prettiest code and it's not as type-safe as if ActionScript had delegates to enforce the signatures of what's set for myFunc, but it is still a pretty flexible model if you get creative with it.
Have those pointers point to something that contains the object or has the object as a property on it.
So
var myArr:Array = [new YourObject()];
var client1:ArrayClient = new ArrayClient();
client1.array = myArr;
var client2:ArrayClient = new ArrayClient();
client2.array = myArr;
myArr[0] = new YourObject();
//client1.array[0]==client2.array[0]==the new object
However, this seems to be a great way to get yourself into a lot of trouble really quickly. What's the use case?