Whack-A-Mole

Overview

A super simple Whack-A-Mole game.

Try It

Use the following Loom CLI commands to run this example:

loom new MyWhackAMole --example WhackAMole
cd MyWhackAMole
loom run

Screenshot

WhackAMole Screenshot

Code

src/WhackAMole.ls

package
{

    import loom.Application;    
    import loom.platform.Timer;

    import loom2d.Loom2D;
    import loom2d.display.Image;        
    import loom2d.display.StageScaleMode;
    import loom2d.animation.Transitions;
    import loom2d.textures.Texture;
    import loom2d.ui.SimpleLabel;
    import loom2d.events.Touch;
    import loom2d.events.TouchEvent;
    import loom2d.events.TouchPhase;

    import loom2d.math.Point;

    public class WhackAMole extends Application
    {
        protected var timer:Timer;
        protected var moles:Vector.<Image>;
        protected var moleStates:Vector.<Boolean>;
        protected var scores:Vector.<SimpleLabel>;
        protected var misses:Vector.<SimpleLabel>;
        protected var waitTime:Number;
        protected var totalScore:SimpleLabel;
        protected var total:Number;
        protected var strikes:Number;
        protected var retryButton:Image;
        protected var timeLabel:SimpleLabel;
        protected var gameTimer:Timer;

        override public function run():void
        {

            stage.scaleMode = StageScaleMode.FILL;

            var screenWidth = stage.stageWidth;
            var screenHeight = stage.stageHeight;            

            waitTime = 0.5;
            strikes = 0;

            var ground = new Image(Texture.fromAsset("assets/background/bg_dirt.png"));
            ground.x = 0;
            ground.y = 0;
            ground.touchable = false;
            stage.addChild(ground);

            var top = new Image(Texture.fromAsset("assets/foreground/grass_upper.png"));
            top.x = 0;
            top.y = 0;
            top.height = 160;
            top.width = 480;
            top.touchable = false;
            stage.addChild(top);

            var mole1 = new Image(Texture.fromAsset("assets/sprites/mole_1.png"));
            mole1.x = 50;
            mole1.y = 180;
            mole1.scale = 0.5;
            stage.addChild(mole1);

            mole1.addEventListener( TouchEvent.TOUCH, function(e:TouchEvent) { 
                if (e.getTouch(mole1, TouchPhase.BEGAN)) {                    
                    whackMole(e, mole1); 
                } } );            

            var mole2 = new Image(Texture.fromAsset("assets/sprites/mole_1.png"));
            mole2.x = 195;
            mole2.y = 180;
            mole2.scale = 0.5;
            stage.addChild(mole2);

            mole2.addEventListener( TouchEvent.TOUCH, function(e:TouchEvent) { 
                if (e.getTouch(mole2, TouchPhase.BEGAN)) {                    
                    whackMole(e, mole2);
                } } );            

            var mole3 = new Image(Texture.fromAsset("assets/sprites/mole_1.png"));
            mole3.x = 340;
            mole3.y = 180;
            mole3.scale = 0.5;
            stage.addChild(mole3);

            mole3.addEventListener( TouchEvent.TOUCH, function(e:TouchEvent) { 
                if (e.getTouch(mole3, TouchPhase.BEGAN)) {                    
                    whackMole(e, mole3);
                } } );            

            moles = [mole1, mole2, mole3];
            moleStates = [false, false, false];

            var bottom = new Image(Texture.fromAsset("assets/foreground/grass_lower.png"));
            bottom.x = 0;
            bottom.y = 160;
            bottom.width = 480;    
            bottom.height = 160;   
            bottom.touchable = false; 
            stage.addChild(bottom);

            total = 0;
            totalScore = new SimpleLabel("assets/Curse-hd.fnt");
            totalScore.text = "Score: 0";
            totalScore.x = screenWidth/2 - totalScore.size.x*.5/2;
            totalScore.y = 16;
            totalScore.scale = .5;
            totalScore.touchable = false;
            stage.addChild(totalScore);

            retryButton = new Image(Texture.fromAsset("assets/retry.png"));
            retryButton.x = screenWidth/2 - retryButton.width*.5/2;
            retryButton.y = screenHeight - 24 - retryButton.height/2;
            retryButton.scale = 0.5;

            retryButton.addEventListener( TouchEvent.TOUCH, function(e:TouchEvent) { 
                if (e.getTouch(retryButton, TouchPhase.BEGAN))
                {
                    resetGame();
                    e.stopImmediatePropagation();
                }
            } );            

            timeLabel = new SimpleLabel("assets/Curse-hd.fnt");
            timeLabel.text = "30";
            timeLabel.x = 410;
            timeLabel.y = 16;
            timeLabel.scale = 0.5;
            timeLabel.touchable = false;
            stage.addChild(timeLabel);

            gameTimer = new Timer(30000);
            gameTimer.onComplete = endGame;
            gameTimer.start();

            timer = new Timer(1000);
            timer.onComplete = onTimerComplete;
            timer.start();

            stage.addEventListener( TouchEvent.TOUCH, function(e:TouchEvent) { 

                var touch = e.getTouch(stage, TouchPhase.BEGAN);

                if (!touch)
                    return;                

                onMiss(touch.globalX, touch.globalY);

            } );            

            createScoreLabels();
        }

        override public function onTick()
        {
            timeLabel.text = (30-Math.round(gameTimer.elapsed/1000)).toString();
        }

        protected function createScoreLabels()
        {
            // create a pool a score labels to pull from
            scores = new Vector.<SimpleLabel>();
            misses = new Vector.<SimpleLabel>();
            for(var i = 0; i<4; i++)
            {
                var score = new SimpleLabel("assets/Curse-hd.fnt");
                score.text = "+100";
                score.scale = 0.5;
                score.y = 400;
                score.touchable = false;
                scores.push(score);
                stage.addChild(score);

                var miss = new SimpleLabel("assets/Red-hd.fnt");
                miss.text = "miss";
                miss.y = 400;
                miss.touchable = false;
                misses.push(miss);
                stage.addChild(miss);
            }
        }

        protected function getAvailableScoreLabel():SimpleLabel
        {
            for(var i = 0; i<scores.length; i++)
            {
                var score = scores[i];
                if(!Loom2D.juggler.containsTweens(score))
                    return score;
            }

            // default, return the first one
            Loom2D.juggler.removeTweens(scores[0]);
            return scores[0];
        }

        protected function getAvailableMissLabel():SimpleLabel
        {
            for(var i = 0; i<misses.length; i++)
            {
                var miss = misses[i];
                if(!Loom2D.juggler.containsTweens(miss))
                    return miss;
            }

            // default, return the first one
            Loom2D.juggler.removeTweens(misses[0]);
            return misses[0];
        }

        protected function onTimerComplete(timer:Timer)
        {
            for(var i = 0; i<moles.length; i++)
            {
                if(Math.floor(Math.random()*4) == 0)
                {
                    var mole = moles[i];

                    if(moleStates[i] == true) {
                        moleStates[i] = false;
                        mole.source = "assets/sprites/mole_1.png";
                    }

                    if(!Loom2D.juggler.containsTweens(mole))
                    {
                        Loom2D.juggler.tween(mole, 0.5, {"y": 85, "transition": Transitions.EASE_OUT});
                        Loom2D.juggler.tween(mole, 0.3, {"y": 180, "transition": Transitions.EASE_OUT, "delay": 0.5+waitTime});
                    }
                }
            }

            // play the timer again
            timer.start();
        }

        protected function whackMole(e:TouchEvent, mole:Image)
        {

            if(strikes == 3) 
                return;

            var index = moles.indexOf(mole);

            if(moleStates[index] == false && mole.y < 125) 
            {
                // increase the difficulty as we get more moles
                waitTime *= 0.9;
                timer.delay *= 0.9;

                // update our whacked state
                moleStates[index] = true;

                // animate a score
                var score = getAvailableScoreLabel();
                score.x = mole.x;
                score.y = mole.y;
                score.scale = 0;

                total += 100;
                totalScore.text = "Score: " + total.toString();
                // center
                totalScore.x = stage.stageWidth/2 - totalScore.size.x*.5/2;

                Loom2D.juggler.tween(score, 0.3, {"scaleX": 0.5, "transition": Transitions.EASE_OUT_BACK});
                Loom2D.juggler.tween(score, 0.3, {"scaleY": 0.5, "transition": Transitions.EASE_OUT_BACK});
                Loom2D.juggler.tween(score, 0.3, {"y": -100, "transition": Transitions.EASE_IN_BACK, "delay": 0.3});

                Loom2D.juggler.removeTweens(mole);
                mole.source = "assets/sprites/mole_thump4.png";
                Loom2D.juggler.tween(mole, 0.3, {"y": 180, "transition": Transitions.EASE_OUT, "delay": 0.1});

                // stop the event propogating to the miss handler
                e.stopImmediatePropagation();
            }
        }

        protected function onMiss(x:Number, y:Number)
        {
            if(strikes == 3) 
                return;

            strikes++;

            var miss = getAvailableMissLabel();
            miss.x = x - miss.width/2;
            miss.y = y - miss.height/2;
            miss.scale = 0;

            Loom2D.juggler.tween(miss, 0.3, {"scaleX": 0.5, "scaleY": 0.5, "transition": Transitions.EASE_OUT_BACK});
            Loom2D.juggler.tween(miss, 0.3, {"y": -100, "transition": Transitions.EASE_IN_BACK, "delay": 0.3});

            if(strikes == 3) {
                endGame();
            }
        }

        protected function endGame(t:Timer=null)
        {
            strikes = 3;
            stage.removeChild(timeLabel);
            timer.stop();
            gameTimer.stop();

            if(retryButton.parent == null)
                stage.addChild(retryButton);
        }

        protected function resetGame()
        {
            stage.addChild(timeLabel);
            strikes = 0;
            total = 0;
            totalScore.text = "Score: 0";
            totalScore.x = stage.stageWidth/2 - totalScore.size.x*.5/2;
            timer.start();
            gameTimer.start();
            stage.removeChild(retryButton);
            waitTime = 0.5;
            timer.delay = 1000;
        }
    }
}