I'm trying to come up with a clean function that will perform a check on a 2D Array to verify if a win has occurred in a ConnectFour game. I'm trying to work through the logic but my brain is swiss cheese right now. Here's what I have for checking for a horizontal win. It seems to me like there should be a way to streamline this block to work in all possible directions.
private function checkForHorizontalWin(column:uint, row:uint, grid:Array):Boolean
{
var player:uint = grid[column][row];
var counter:uint = 1;
for(var i:uint = Math.min(0, uint(column-1)); i>=0; i--)
{
if(!grid[i][row] || grid[i][row] != player)
{
break;
}
counter++;
}
for(var j:uint = column+1; j<_columns; j++)
{
if(!grid[j][row] || grid[j][row] != player)
{
break;
}
counter++;
}
if(counter >=4)
{
return true;
}
else
{
return false;
}
}
I actually ended up doing something pretty simple...Converted the grid array to a string then look for 4 in a row...Only tricky part was getting the correct array for the diagonal possibilities but I got that worked out too.
Related
I'm going crazy with this. I have a few bears on the stage and have listed them in an array. I want them to change their animation frame when clicked as long as they are not on the "down" animation frame to begin with. Below is the code. The problem occurs below the //check if they get hit line.
//put the bears in an array
var bearsArray: Array = new Array();
for (var i=0; i<numChildren;i++) {
if (getChildAt (i) is bear_mc) {
bearsArray.push(getChildAt(i));
}
}
//move them up and down
addEventListener(Event.ENTER_FRAME,upAndDown);
function upAndDown(event:Event){
if (Math.random() < 0.02){
var randomBear = Math.floor(Math.random() * 9);
bearsArray[randomBear].gotoAndPlay("popup");
}
}
//check if they get hit
for (var j:int = 0; j < bearsArray.length; j++){
bearsArray[j].addEventListener(MouseEvent.CLICK, hitBears);
}
function hitBears(e:MouseEvent){
for (var k: int=0; k<numChildren; k++){
if (bearsArray[k].currentFrame != "down"){
trace("clicked");
bearsArray[k].gotoAndPlay("surprised");
}
}
}
currentFrame returns an integer, not string. If you want to use string, you have to use currentFrameLabel or currentLabel.
EDIT: http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/display/MovieClip.html#currentLabel
If you want to control only one bear, remember that you don't need to cycle through them (actually that would serve no purpose as you would be checking every one of them). The better (and correct) approach would be this:
function hitBears(e:MouseEvent){
var bear:MovieClip = e.currentTarget as MovieClip;
if(bear.currentLabel != "down") { //or currentFrameLabel, depends how your mc is set up
trace("clicked");
bear.gotoAndPlay("surprised");
}
}
I got it working by checking a hittest with the mouse instead of just checking the click. That works for some reason.
I am a beginner/intermediate AS3 "programmer" trying to complete a skeeball-like flash-based game for a university assessment, and I'm doing myself serious mental damage trying to get even basic object (ball) collisions working with scoring targets on my stage. I'm using a power bar type variable to determine the force of the ball roll which is translated into a tween to create a smooth movement up my "lane" and into the scoring area (this is overhead perspective). The targets are instances of movie clips inside a larger movie clip that is made up of all the components of the game table. Even though my game table and scoring components have already been instantiated when I release the ball, I am getting the typical non-null errors:
ballSpeed is 552
targetArray value is [object upperScoringAreaCTarget_mc]
TypeError: Error #2007: Parameter hitTestObject must be non-null.
at flash.display::DisplayObject/_hitTest()
at flash.display::DisplayObject/hitTestObject()
at SkeeBlast_7_fla::MainTimeline/ballTargetScore()
at SkeeBlast_7_fla::MainTimeline/rollBall()
at SkeeBlast_7_fla::MainTimeline/releaseBall()
Here is my ball release function:
function releaseBall(event:MouseEvent):void
{
rollBall();
gameElements.removeEventListener(MouseEvent.MOUSE_MOVE, moveBall);
}
function rollBall():void
{
ballSpeed = rollPower * 12;
trace("ballSpeed is " + ballSpeed);
ballFriction();
ballGravity();
//ball.y -= ballSpeed;
//var myBallTween:Tween = new Tween(ball,"y",Strong.easeOut,ball.y,ball.y - ballSpeed,3,true);
myBallTween = new Tween(ball,"y",Strong.easeOut,ball.y,ball.y - ballSpeed,3,true);
myBallTween.start();
ballTargetScore();
}
and my collision detection and scoring function:
//match targets to scoring values which should be tied to determineScore()
function ballTargetScore():void
{
var targetValue:String;
var targetArray:Array = new Array(gameTable.upperScoringArea.upperScoringAreaCTarget,
gameTable.upperScoringArea.upperScoringAreaLtTarget,
gameTable.upperScoringArea.upperScoringAreaRtTarget,
gameTable.middleScoringArea.middleScoringAreaTargetTop,
gameTable.middleScoringArea.middleScoringAreaTargetMiddle,
gameTable.middleScoringArea.middleScoringAreaTargetLower,
gameTable.lowerScoringArea.lowerScoringAreaTarget);
if (ball.hitTestObject(gameTable.tableDisplay))
{
myBallTween.stop();
}
else
{
for (var i:uint; i < targetArray.length; i++)
{
if (targetArray[i] != null)
{
trace("targetArray value is " + targetArray[i]);
if (ball.hitTestObject(gameTable.upperScoringArea.upperScoringAreaCTarget))
{
targetValue = gameTable.upperScoringArea.upperScoringAreaC_text.text;
}
if (ball.hitTestObject(gameTable.upperScoringArea.upperScoringAreaLtTarget))
{
targetValue = gameTable.upperScoringArea.upperScoringAreaLt_text.text;
}
if (ball.hitTestObject(gameTable.upperScoringArea.upperScoringAreaRtTarget))
{
targetValue = gameTable.upperScoringArea.upperScoringAreaRt_text.text;
}
if (ball.hitTestObject(gameTable.upperScoringArea.middleScoringAreaTargetTop))
{
targetValue = gameTable.middleScoringArea.middleScoringAreaU_text.text;
}
if (ball.hitTestObject(gameTable.upperScoringArea.middleScoringAreaTargetMiddle))
{
targetValue = gameTable.middleScoringArea.middleScoringAreaM_text.text;
}
if (ball.hitTestObject(gameTable.upperScoringArea.middleScoringAreaTargetLower))
{
targetValue = gameTable.middleScoringArea.middleScoringAreaL_text.text;
}
if (ball.hitTestObject(gameTable.upperScoringArea.lowerScoringAreaTarget))
{
targetValue = gameTable.lowerScoringArea.lowerSA_text.text;
}
else
{
trace("no hit");
}
}
}
}
//gameElements.removeEventListener(Event.ENTER_FRAME, ballTargetScore);
determineScore(targetValue);
//return targetValue;
}
It's all still a bit messy as I tinker trying to find the right combination.
I need to first get a basic ball<->target or boundary collision working, but ultimately, I'd like to try to figure out how to control the collisions conditionally since many of the targets are aligned vertically like a skeeball table. Will I be able to measure the speed of the ball as it is tweening/rolling? Will I be able to apply gravity correctly so that the ball falls and if it catches one of the arcs beneath the scoring target, it will roll along the arc until it 'falls into the hole'? I think I will have issues with the bounding box of my objects on that one.
Thanks for any help,
Alan
Here's the response to Vesper with a cleaned up function (thanks) and new error.
function ballTargetScore():void
{
var targetValue:String;
var targetArray:Array = new Array(gameTable.upperScoringArea.upperScoringAreaCTarget,
gameTable.upperScoringArea.upperScoringAreaLtTarget,
gameTable.upperScoringArea.upperScoringAreaRtTarget,
gameTable.middleScoringArea.middleScoringAreaTargetTop,
gameTable.middleScoringArea.middleScoringAreaTargetMiddle,
gameTable.middleScoringArea.middleScoringAreaTargetLower,
gameTable.lowerScoringArea.lowerScoringAreaTarget);
var targetTextArray:Array = new Array(gameTable.upperScoringArea.upperScoringAreaC_text.text,
gameTable.upperScoringArea.upperScoringAreaLt_text.text,
gameTable.upperScoringArea.upperScoringAreaRt_text.text,
gameTable.middleScoringArea.middleScoringAreaU_text.text,
gameTable.middleScoringArea.middleScoringAreaM_text.text,
gameTable.middleScoringArea.middleScoringAreaL_text.text,
gameTable.lowerScoringArea.lowerSA_text.text);
for (var i:uint; i < targetArray.length; i++)
{
if (targetArray[i] != null)
{
trace("targetArray value is " + targetArray[i]);
if (ball.hitTestObject(targetArray[i]))
{
targetValue = targetTextArray[i];
trace('targetValue becomes',targetValue);
}
}
}
determineScore(targetValue);
}
and the error:
ballSpeed is 432
targetArray value is [object upperScoringAreaCTarget_mc]
targetArray value is [object upperScoringAreaLtTarget_mc]
targetArray value is [object upperScoringAreaRtTarget_mc]
targetArray value is [object middleScoringAreaTargetTop_mc]
targetArray value is [object middleScoringAreaTargetMiddle_mc]
targetArray value is [object middleScoringAreaTargetLower_mc]
targetArray value is [object lowerScoringAreaTarget_mc]
ArgumentError: Error #2025: The supplied DisplayObject must be a child of the caller.
at flash.display::DisplayObjectContainer/removeChild()
at SkeeBlast_7_fla::MainTimeline/determineScore()
at SkeeBlast_7_fla::MainTimeline/ballTargetScore()
at SkeeBlast_7_fla::MainTimeline/rollBall()
at SkeeBlast_7_fla::MainTimeline/releaseBall()
Thanks for the assistance.
The rules, which I'm sure are necessary, are making me turn this into a really long question. :)
Anyway, I sort of figured out the error about the DisplayObject, but the program still isn't working correctly.
I fixed the error by adding my gameElements MC to my removeChild call in the determineScore function, which is below; however, the ball still isn't removed, even with the error gone, and the targetValue and the score never update (or show up in the trace from above).
function determineScore(scoreEvent:String):void
{
if ( scoreEvent == "D-O" || scoreEvent == "2XB" || scoreEvent == "?")
{
if (scoreEvent == "D-O")
{
ballCount += 1;
gameTable.tableDisplay.BR_text.text = ballCount - 1;
gameTable.tableDisplay.BR_text.embedFonts = false;
gameElements.removeChild(ball);
if (ballCount > 0 )
{
initBall();
}
else
{
drawEnd();
}
}
else if (scoreEvent == "2XB")
{
ballCount += 2;
gameTable.tableDisplay.BR_text.text = ballCount - 1;
gameTable.tableDisplay.BR_text.embedFonts = false;
gameElements.removeChild(ball);
if (ballCount > 0 )
{
initBall();
}
else
{
drawEnd();
}
}
else
{
determineScore(allRandomScore(allScoresArray));
}
}
else
{
scoreTotal += Number(scoreEvent);
ballScore = Number(scoreEvent);
gameTable.tableDisplay.Score_text.text = scoreTotal;
gameTable.tableDisplay.Score_text.embedFonts = false;
gameElements.removeChild(ball);
if (ballCount > 0 )
{
initBall();
}
else
{
drawEnd();
}
}
}
Thanks for looking at this. I feel like I'm finally making a little bit of progress.
Ok, continuing along with yet another update.
gameElements is the primary empty MC that I add all the other MC, like the game table and the ball, to so that when I go back to the main menu, I can remove everything at once.
var gameElements:MovieClip = new MovieClip();
var gameTable:MovieClip = new table_mc();
var ball:MovieClip = new blastBall();
and from my drawGame function:
...
stage.addChildAt(gameElements, 1);
gameElements.addChild(gameTable);
initBall();
...
and initBall:
function initBall():void
{
//resize ball
ball.height = 18;
ball.width = 18;
//place ball on table in correct location
ball.x = gameTable.x;
ball.y = gameTable.height - 20;
gameElements.addChild(ball);
//reduce number of remaining balls;
ballCount -= 1;
//hide the mouse and connect its movement to ball
Mouse.hide();
gameElements.addEventListener(MouseEvent.MOUSE_MOVE, moveBall);
}
Hope there's no limit to this entry. :)
Here's the little addition I put in determineScore to pick up the "results" from ballTargetScore (or lack thereof, really):
if (scoreEvent == null)
{
trace("scoreEvent is null");
gameTable.tableDisplay.BR_text.text = ballCount - 1;
gameTable.tableDisplay.BR_text.embedFonts = false;
gameElements.removeChild(ball);
if (ballCount > 0)
{
initBall();
}
else
{
drawEnd();
}
}
(haven't cleaned up anything else yet) Still trying to get that first collision to work. When I started catching the null value, initBall and drawEnd sort of started to work (ok, it still doesn't really do what I want but at least there was a response).
Well, you have an array of your targets, and the error you receive means that some of its elements are null. But, when you iterate through the array, you for some reason check against the entire set of targets, not checking if any of them are null. I don't understand why. Seeing that you need a text variable to be assigned if a hit is detected, I advise you making another array with corresponding indexes that will contain all the texts you want to assign.
var targetTextArray:Array=(gameTable.upperScoringArea.upperScoringAreaC_text.text,
gameTable.upperScoringArea.upperScoringAreaLt_text.text,
gameTable.upperScoringArea.upperScoringAreaRt_text.text,
gameTable.middleScoringArea.middleScoringAreaU_text.text,
gameTable.middleScoringArea.middleScoringAreaM_text.text,
gameTable.middleScoringArea.middleScoringAreaL_text.text,
gameTable.lowerScoringArea.lowerSA_text.text);
Then, when you iterate through targetArray, you no longer need to check what was that target in order to retrieve the correct text. You do:
for (var i:uint; i < targetArray.length; i++)
{
if (targetArray[i] != null)
{
trace("targetArray value is " + targetArray[i]);
if (ball.hitTestObject(targetArray[i])) {
targetValue=targetTextArray[i];
trace('targetValue becomes',targetValue);
}
}
}
Update: Okay, let's clean up your determineScore() function. It looks like you are correctly adding and removing your ball, but it's possible you do something of this twice. Removing the ball twice without adding it back will net you this error. Currently I can't get if gameElements.removeChild(ball); gets called twice within that function.
function determineScore(scoreEvent:String):void
{
var reBall:Boolean=false; // should we make another ball roll, given the score
var reDisplay:Boolean=false; // should we set new text
if ((scoreEvent==null)||(scoreEvent=="")) return; // we haven't scored
if ( scoreEvent == "D-O" || scoreEvent == "2XB" || scoreEvent == "?")
{
if (scoreEvent == "D-O")
{
ballCount += 1;
reBall=true;
reDisplay=true;
}
else if (scoreEvent == "2XB")
{
ballCount += 2;
reBall=true;
reDisplay=true;
}
else
{
determineScore(allRandomScore(allScoresArray));
// hey, what are we doing here? We seemingly receive a "?" as an input,
// and we roll a random value out of a set given elsewhere. Okay
return; // as good measure, because we don't have to do anything more
}
}
else
{ // if we're here, we assume we have a numeric score passed inside, right?
scoreTotal += Number(scoreEvent);
ballScore = Number(scoreEvent);
reBall=true;
reDisplay=true;
}
// Now look, we always have to launch a new ball or display end, and
// we always have to redraw score textfield. But look, you are always calling
// determineScore with some value, while you should do it only if you hit something!
// So I added starting clause of "if null or empty string".
if (reDisplay) {
// we have to update text
gameTable.tableDisplay.BR_text.text = ballCount - 1;
gameTable.tableDisplay.BR_text.embedFonts = false;
}
if (reBall) {
// we have to remove ball and launch another, if we have any left
gameElements.removeChild(ball);
if (ballCount > 0 )
{
initBall();
}
else
{
drawEnd();
}
}
}
I have an issue here. Every five seconds the spawnEnemies is fired. It creates a movieclip, sets it position and adds it to the stage and an array. In the function checkBullet I check if the bullet hits one of the enemies. If it does, the bullet and the enemy are removed from the stage, and the enemy is spliced from the array. But an error is thrown once I hit an enemy:
TypeError: Error #1010: A term is undefined and has no properties.
If I comment out the arrayEnemies.splice(i,1) line, it works fine but then it isn't removed from the array. This is in itself not an issue, but naturally I don't like to keep unnecessary data in my array. Any help on this?
function checkBullet(event:Event) {
if(stage.contains(mcBullet)) {
for(var i:int = 0; i < arrayEnemies.length; i++) {
if(arrayEnemies[i].hitTestPoint(mcBullet.x, mcBullet.y, true)) {
stage.removeChild(mcBullet);
this.removeChild(arrayEnemies[i]);
arrayEnemies.splice(i,1);
bulletOnStage = false;
}
}
if(mcBullet.x > 800) {
stage.removeChild(mcBullet);
bulletOnStage = false;
}
}
}
function spawnEnemies(event:TimerEvent) {
var enemie:MovieClip = new box_mc();
enemie.name = "mcBox" + event.currentTarget.currentCount;
enemie.x = 850;
enemie.y = Math.floor(Math.random()*(1+155)+255);
addChild(enemie);
arrayEnemies.push(enemie);
}
function moveEnemies(event:Event) {
for(var i:int = 0; i < arrayEnemies.length; i++) {
arrayEnemies[i].x -= 5;
}
}
This will be caused by working on an array that you are interating through, you should hold a ref to the stuff you want to remove then do it after the loop.
Your problem is that if the bullet hits two enemies, you try to remove it from the stage twice. This will throw an ArgumentError.
If you need to test against all enemies (assuming multiple enemies can be hit by the same bullet), you also need to decrement i when you remove an element from your enemy array.
function checkBullet(event:Event) {
if(stage.contains(mcBullet)) {
if(mcBullet.x > 800) {
stage.removeChild(mcBullet);
bulletOnStage = false;
}
for(var i:int = 0; i < arrayEnemies.length; i++) {
if(arrayEnemies[i].hitTestPoint(mcBullet.x, mcBullet.y, true)) {
if(stage.contains(mcBullet)){
stage.removeChild(mcBullet);
}
this.removeChild(arrayEnemies[i]);
arrayEnemies.splice(i,1);
bulletOnStage = false;
i--;
}
}
}
}
Bit of an older question but thought I'd throw in my answer too for anyone that might end up here.
Like Neil said, editing an array that you're itterating through (in this case arrayEnemies) can cause concurrent update problems.
My prefered solution is to use a seperate toRemove array and remove them after the update, for example:
var enemies:Array();
//Lets assume at some point this is populated with Enemy objects
function update():void
{
var toRemove:Array = new Array();
//May want to keep and clear an array instead to avoid creating a new one each update
foreach(var enemy:Enemy in enemies)
{
enemy.update();
if(enemy.markedForRemoval())
toRemove.push(enemy);
}
foreach(var enemy:Enemy in toRemove)
enemies.splice(enemies.indexOf(enemy), 1);
}
I am creating a simple image viewer using AS3 and getting image URLs from flashVars in the HTML. I want to use an if
argument to have it perform one function if there is only one flashVar and another if there are more.
I have it reading the flashVars fine and can write if statments all day long, the trouble I am having is counting the number
of flashVars being passed from the HTML. here is a real dumbed down version of what I am trying to do (which doesnt work
because I can not figure out the correct process to follow):
var numberOfVars:Number = 0; // to store the number of flashVars
var paramObj:Object = LoaderInfo(this.root.loaderInfo).parameters; //get the flashVars
for each (paramObj in LoaderInfo(this.root.loaderInfo).parameters){
numberOfVars + 1;
}
var tf:TextField = new TextField();
addChild(tf);
tf.text = numberOfVars.toString(); // this returns '0' when runningn this code
if (numberOfVars < 2){
//do this
}
else {
//do this
}
thanks for your help guys.
Chris
You would want to increment numberOfVars
Looks like you have this:
numberOfVars + 1;
When you should have this:
numberOfVars++;
which is just short way of writing this:
numberOfVars = numberOfVars + 1;
or this:
numberOfVars += 1;
Just use a for(key in object) loop to track the amount of vars stored on root.loaderInfo.parameters.
var fvars:Object = root.loaderInfo.parameters;
var n:int = 0;
for(var i:String in fvars) n ++;
// n holds the amount of flashvars
if(n <= 1)
{
// one or no variables found
}
else
{
// more than one found
}
Also, you've done one of your operators wrong:
for each(paramObj in LoaderInfo(this.root.loaderInfo).parameters)
{
numberOfVars + 1; // <<<<<< here
}
You're looking for one of the following:
numberOfVars ++;
numberOfVars += 1;
numberOfVars = numberOfVars + 1;
I will offer a slightly different take on the conditional logic here. Instead of reacting to variables based on how many there are (introducing plenty of opportunity for unpredictable results) - why not base your conditional statements on what the vars actually are. This is my standard flashVar process:
var _loadParams:Object
private function init():void
{
_loadParams = new Object();
_loadParams = LoaderInfo(stage.loaderInfo).parameters;
someVar = String( parseParam( "someVar", "default value") );
}
private function parseParam(name:String, defaultValue:String):String
{
if (_loadParams.hasOwnProperty(name) && _loadParams[name] != "" && _loadParams[name] != "undefined")
{
return _loadParams[name];
}
else
{
return defaultValue;
}
}
This, obviously, is just testing for the presence of a value, but it could be easily altered to call some functionality based on those results:
if (_loadParams.hasOwnProperty(name) && _loadParams[name] != "" && _loadParams[name] != "undefined")
{
switch (name){
case 'param1':
doSomthing(name);
break;
case 'param2':
doSomthingElse(name);
break;
}
}
I don't know what you are trying to achieve, and this may be way off. But I thought I'd chime in ;)
Cheers!
I've got an array of sprites which I'm animating by incrementing their rotationX property. What I want is for them all to disappear once the last item in the array has come full circle. The problem is that their rotation speeds are being generated by a randomized function, so I can't just go to the end of the array to find the last one. Each time it will be a different one.
So I have an array of Sprites:
for(var i:int=0; i<arrSprites.length; i++)
{
addChild(arrSprites[i]) ;
}
Then I have my event listener:
addEventListener(Event.ENTER_FRAME, loop);
And my handler:
private function loop(e:Event):void
{
for(var i:int=0; i<arrSprites.length; i++)
{
var currentSprite:Sprite = arrSprites[i];
if(currentSprite.rotationX < 361) //this will detect the first one
//to finish but I want the last
{
currentSprite.rotationX += arrSprites[i].speed; //random speed
}
else
{
deleteTheSprites(); //removes all sprites and does other stuff
}
}
}
There's got to be an elegant way to do this. Anyone know what it is?
Thanks,
David
private function loop(e:Event):void
{
var finished : int = 0; // will count the number of sprites finished
for each (var current:Sprite in arrSprites)
{
if (current.rotationX < 361) current.rotationX += current.speed;
else if (++finished == arrSprites.length) deleteTheSprites(); // executes only if all sprites have finished
}
}