> show canvas only <


/* built with Studio Sketchpad: 
 *   https://sketchpad.cc
 * 
 * observe the evolution of this sketch: 
 *   https://studio.sketchpad.cc/sp/pad/view/ro.pJJC$lgQ$KU/rev.1175
 * 
 * authors: 
 *   
 *   Michal Wallace

 * license (unless otherwise specified): 
 *   creative commons attribution-share alike 3.0 license.
 *   https://creativecommons.org/licenses/by-sa/3.0/ 
 */ 



/* @pjs preload="/static/uploaded_resources/p.1181/invaders.png"; */
/*
 * InvaderSketch!
 *     by Michal J Wallace
 *     twitter: @tangentstorm
 *
 * Live demo at studio.sketchpad.cc:
 *
 *     http://studio.sketchpad.cc/sp/pad/view/ro.9bi5HFOFBWaAa/latest
 * 
 * Created with GameSketchLib, an open-source game engine for processing.
 *
 *     http://gamesketchlib.org/
 *
 * Video lessons about this game:
 *
 *     http://www.youtube.com/playlist?list=PL9164D8831A48D0DE&feature=viewall
 *
 * This game was inspired by the legendary arcade classic,
 * "Space Invaders" by Tomohiro Nishikado (Taito, 1978)
 *
 *     http://en.wikipedia.org/wiki/Space_Invaders
 *
 */

String PJS_URL = "/static/uploaded_resources/p.1181";

void setup()
{
    // DEBUG = true;
    
    SHEET = new GameSheet("invaders.png", 50, 50, PJS_URL);
    
    Game.size(640, 480);
    Game.init(DEBUG ? new PlayState()
                    : new MenuState());
}


class CrazyText extends GameText
{
    CrazyText(String label, float x, float y, color c, int fontSize)
    {
        super(label, x, y, c, fontSize);
    }
    
    private GameTimer mCrazyTimer = new GameTimer(SECONDS/10);
    public void render()
    {
       if (mCrazyTimer.checkReady()) 
           this.textColor = color(127 + random(127), 127 + random(127), 127 + random(127));
       super.render();               
    }
}


class MenuState extends GameState
{
   GameLink mLink;
   
   void create()
   {
       add(new CrazyText("InvaderSketch!", Game.bounds.w /2, 100, #FFFFFF, 48));
       add(new GameText("Part of the GameSketchLib Tutorial Series",
                        Game.bounds.w /2, 150, #CCCCCC, 12));
   
       mLink = (GameLink) add(new GameLink(
              "www.GameSketchLib.org",
              "http://gamesketchlib.org",
              Game.bounds.w /2, 200, #9999FF, 18));
              
       add(new GameText("Use the Arrow Keys to Move, Space to Shoot",
                        Game.bounds.w / 2, 300, #FFFFFF, 18));
                        
       add(new GameText("Press Space to Start",
                        Game.bounds.w / 2, 360, #CCCCCC, 18));
   }
   
   void update()
   {
       if (Game.keys.justPressed(SPACE))
       {
           Game.switchState(new PlayState());
       }
   }
   
   void mouseMoved()
   {
       mLink.hover = mLink.bounds.containsPoint(mouseX, mouseY);
       cursor( mLink.hover ? HAND : ARROW );
   }
   
   void mousePressed()
   {
       if (mLink.hover) mLink.click();
   }
   
}


class GameOverState extends GameState
{
   void create()
   {
       add(new GameText("Game Over!",
                        Game.bounds.w /2, 100, #FFFFFF, 18));
                        
       add(new GameText("Press space to Restart",
                        Game.bounds.w / 2, 150, #CCCCCC, 12));
   }
   
   void update()
   {
       if (Game.keys.justPressed(SPACE))
       {
           Game.switchState(new MenuState());
       }
   }
}



class WinState extends GameOverState
{
   void create()
   {
       add(new GameText("You Won!",
                        Game.bounds.w /2, 100, #FFFFFF, 18));
                        
       add(new GameText("Press space to Restart",
                        Game.bounds.w / 2, 150, #CCCCCC, 12));
   }
}


//== PlayState ========================================================

class PlayState extends GameState
{
  
    GameGroup mInvaders = new GameGroup();
    GameGroup mShipInvaders = new GameGroup();
    GameGroup mHeroBullets = new GameGroup();
    GameGroup mEnemyBullets = new GameGroup();
    GameGroup mShields = new GameGroup();
    
    // There's no GameGroup.overlap(GameSprite) yet:
    GameGroup mHeroGroup = new GameGroup();
    HeroSprite mHero;
    final float kHeroSpeed = 3.5;
    final int kBulletCount = 3;
    final float kBulletSpeed = -1.75;
    int mBulletsLeft = kBulletCount;
    int kBulletW = 10;
    int kBulletH = 50;
    
    void create()
    {
      
       // create the hero and put him in his group       
       mHero = new HeroSprite(Game.bounds.w/2 - 25, Game.bounds.h - 50);
       mHeroGroup.add(mHero);
       
       // our bullets and fixed ammo display from BulletDemo
       createHeroBullets();

       // generate the enemies:
       int row = 0;
       for (int y = 25; y < Game.bounds.h - 100; y += 75, row++)
       {
         for (int x = 65; x < Game.bounds.w - 50; x += 75)
         {
             switch(row)
             {
               case 0:
                 mInvaders.add(mShipInvaders.add(new ShipInvader(x, y)));
                 break;
               case 1:
               case 3:
                 mInvaders.add(new SpinInvader(x, y));
                 break;
               case 2:
                 mInvaders.add(new JellInvader(x, y));
                 break;
               default:
                 // only do 4 rows
             }
         }
       }
       
       // create the shields:
       int[] shieldXs = new int[] { 50, 100, 150, 250, 300, 350, 450, 500, 550 };
       for (int i = 0; i < shieldXs.length; ++i)
       {
           mShields.add(new Shield(shieldXs[i], Game.bounds.h - 125));
       }
       
       
       // add the groups to the state:
       // add the bullets first so they appear behind when shooting
       add(mHeroBullets);
       add(mEnemyBullets);
       add(mHeroGroup);
       add(mShields);
       add(mInvaders);
    }
    
    void createHeroBullets()
    {  
        for (int i = 0; i < kBulletCount; ++i)
        {
            Bullet b = (Bullet) mHeroBullets.add(new Bullet(0,0));
            b.dy = kBulletSpeed;
        }
    }
    
    
    void update()
    {
        super.update();
        
        if (Game.keys.justPressed(SPACE)) { shoot(); }
        if (Game.keys.goW()) { mHero.x -= kHeroSpeed; }
        if (Game.keys.goE()) { mHero.x += kHeroSpeed; }
        mHero.x = GameMath.clamp(mHero.x, 0, Game.bounds.w - mHero.w);
        
        if (Game.keys.justPressed('r'))
        {
            GameSprite g = (GameSprite) mShipInvaders.atRandom();
            g.degrees = (int) random(360);
        }
        
        updateHeroBullets();
        updateInvaders();
        enemyFire();
        updateShields();
        
        // checkForWin:    
        if (mInvaders.firstAlive() == null) { Game.switchState(new WinState()); }
    }
    
    void shoot()
    {
        if (mBulletsLeft > 0)
        {
            mBulletsLeft--;
            Bullet b = (Bullet) mHeroBullets.firstDead();
            b.fire(mHero.x, mHero.y);
        }
    }
    
    void updateHeroBullets()
    {
         mHeroBullets.overlap(mInvaders);
         mHeroBullets.overlap(mShields);
         
         int bulletsLeft = 0;
         for (int i = 0; i < kBulletCount; ++i)
         {
             Bullet b = (Bullet) mHeroBullets.get(i);
             b.update();
             if (! b.overlaps(Game.bounds)) b.alive = false;
             if (! b.alive)
             {
                 b.x = kBulletW * bulletsLeft++;
                 b.y = Game.bounds.h - kBulletH;
             }
         }
         mBulletsLeft = bulletsLeft;
    }
    
    
    GameTimer mShiftTimer = new GameTimer(0.1 * SECONDS);
    int mShiftCount = 0;
    int mDriftX = 0;
    int mFleetSpeedX = 2;  // pixels per tick
    int mFleetSpeedY = 10; // pixels every time they switch x directions
    void updateInvaders()
    {
         mInvaders.removeDead();
         mShipInvaders.removeDead();
         
         // left and right shift:
         mShiftTimer.update();
         if (mShiftTimer.ready)
         {
             // move the whole fleet left or right:
             for (int i = 0; i < mInvaders.size(); ++i)
             {
                 GameSprite g = (GameSprite) mInvaders.get(i);
                 g.x += mFleetSpeedX;
             }
             mDriftX += mFleetSpeedX;
             
             // when they hit the edge...
             if (mDriftX > 50 || mDriftX < -50)
             {
                 // turn around:
                 mFleetSpeedX *= -1;
                 
                 // and move down:
                 for (int i = 0; i < mInvaders.size(); ++i)
                 {
                     GameSprite g = (GameSprite) mInvaders.get(i);
                     g.y += mFleetSpeedY;
                     
                     // game over if one gets within 100px of the bottom
                     if (g.y >= Game.bounds.h - g.h * 2) Game.switchState(new GameOverState());
                 }
             }
         }
    }
    
    
    GameTimer mEnemyShotTimer = new GameTimer(4 * SECONDS, true);
    void enemyFire()
    {
         mEnemyBullets.removeDead();
         mEnemyBullets.overlap(mHeroGroup);
         mEnemyBullets.overlap(mShields);

        // only the orange ships at the top shoot
        if (mShipInvaders.size() == 0) return;
        
        if (mEnemyShotTimer.checkReady())
        {
            // randomize the time between shots:
            mEnemyShotTimer.randomize();
            
            // wait a bit to start shooting:
            if (mEnemyShotTimer.tickCount == 1) return;
            
            // pretty much the same as shoot()
            ShipInvader aShip = (ShipInvader) mShipInvaders.atRandom();
            EnemyBullet b = new EnemyBullet(aShip.x, aShip.y);
            b.dy = -kBulletSpeed;
            mEnemyBullets.add(b);
        }        
    }
    
    void updateShields()
    {
        mShields.removeDead();
    }
    
}


class ShipInvader extends GameSprite
{
    ShipInvader(int x, int y)
    {
        super(x, y);
        sheetFrames(new int[] { 1 });
    }    
}



class SpinInvader extends GameSprite
{
    float angleDelta = 2.5;
    
    SpinInvader(int x, int y)
    {
        super(x, y);
        sheetFrames(new int[] { 2, 3 });
        randomize();
        this.degrees = (int) random(-60, 60);
    }
    
    void update()
    {
        super.update();
        
        this.degrees += angleDelta;
        if (this.degrees > 60 || this.degrees < -60)
        {
          angleDelta *= -1;
        }
        
    }
} 

class JellInvader extends GameSprite
{
    float yDrift = 0;
    
    JellInvader(int x, int y)
    {
        super(x, y);
        sheetFrames(new int[] { 8, 9, 10, 11 });
        randomize();
        this.dy = 1;
        this.yDrift = random(-10, 10);
        this.y += yDrift;
    }
    
    void update()
    {
        super.update();
        this.y += dy;
        this.yDrift += dy;
        if (this.yDrift > 10 || this.yDrift < -10)
        {
            this.dy *= -1;
        }
    }
}

class Shield extends GameSprite
{

    Shield(float x, float y)
    {
        super(x, y);
        sheetFrames(new int[] { 12, 13, 14  });
        this.animated = false;
        this.health = 3;
    }
    
    void hurt()
    {
        super.hurt();
        if (alive) frame++; 
    }
}

class HeroSprite extends GameSprite
{
    HeroSprite(float x, float y)
    {
       super(x, y);
       sheetFrames(new int[] { 0 });
    }
    
    void onDeath()
    {
        Game.switchState(new GameOverState());
    }
}


 class Bullet extends GameSprite
 {
     GameObject trueBounds = new GameObject();

     Bullet(float x, float y)
     {
         super(x, y);
         sheetFrames(new int[] { 4 });
         alive = false;
     }
  

     void update()
     {
         if (this.alive)
         {
             y += dy;
             x += dx;
         }
         this.trueBounds.reset(x + 20, y + 20, 10, 15);
         if (! this.trueBounds.overlaps(Game.bounds))
         {
             this.alive = false;
         }
     }

     void fire(float x, float y)
     {
         this.x = x;
         this.y = y;
         this.alive = true;
     }

     boolean overlaps(GameObject other)
     {
         return this.trueBounds.overlaps(other);
     }
     
     void onOverlap(GameObject other)
     {
         this.alive = false;
         other.hurt();
     }
 }

class EnemyBullet extends Bullet
{
     EnemyBullet(float x, float y)
     {
         super(x, y);
         sheetFrames(new int[] { 5 });
         alive = true;
     }
}
/* -----------------------------------------------------------------
 * GameSketchLib                                 compiled 2011-10-15
 * -----------------------------------------------------------------
 * 
 * GameSketchLib is an open-source game library for Processing.
 * 
 * It was created by Michal J Wallace and loosely modeled after
 * the flixel game library for actionscript.
 * 
 *       website: http://gamesketchlib.org/
 *          code: https://github.com/sabren/GameSketchLib
 *       twitter: @tangentstorm
 * 
 * If you want to modify the library, consider forking the git
 * repository at github, rather than editing this combined file.
 * 
 * -----------------------------------------------------------------
 * 
 * GameSketchLib
 * Copyright (c) 2011 Michal J. Wallace
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * 
 */// [Begin GameSketchLib] ---------------------------------------


void draw()
{
    Game.update();
    Game.render();
}



// global debug flag.
// so far, this just shows Bounds for sprites
boolean DEBUG = false;

// a predefined GameSheet, since everyone wants a GameSheet!
GameSheet SHEET;

// Timer Constants
float SECONDS = 1000;

// Keyboard Constants:
char SPACE = ' ';
char[] WASD_N  = new char[] { 'W', ',', '<' };
char[] WASD_W  = new char[] { 'A', 'a' };
char[] WASD_S  = new char[] { 'S', 's', 'O', 'o' };
char[] WASD_E  = new char[] { 'D', 'd', 'E', 'e' };


// global event handlers:
void mousePressed()  { Game.state.mousePressed();  }
void mouseReleased() { Game.state.mouseReleased(); }
void mouseMoved()    { Game.state.mouseMoved();    }
void mouseDragged()  { Game.state.mouseDragged();  }
void keyPressed()    { Game.keys.setKeyDown(true); }
void keyReleased()   { Game.keys.setKeyDown(null); }



// [ Game.pde ]:


void _size(int w, int h) { size(w, h); }

// Game is a Singleton - i.e., the only instance of GameClass.
class GameClass
{
    GameState state;
    GameObject bounds;
    PFont defaultFont;
    GameKeys keys = new GameKeys();
    
    // global timer
    public  float frameMillis = 0;
    private float mLastMillis = 0;
    
    void size(int w, int h)
    {
        if (CONFIG_ANDROID)
        {
            _size(screenWidth, screenHeight);
        }
        else
        {
            _size(w, h);
        }
    }
    
    void init(GameState newState)
    {
        Game.defaultFont = 
            CONFIG_JVM ? loadFont("DejaVuSans-48.vlw")
                       : loadFont("Arial");
        Game.bounds = new GameObject(0, 0, width, height);
        switchState(newState);
    }
    
    void switchState(GameState newState)
    {
        Game.state = newState;
        newState.create();
    }
    
    void update()
    {
        // update the clock:
        float ms = millis();
        this.frameMillis = ms - mLastMillis;
        mLastMillis = ms;
        
        // now update everything else:
        this.state.update();
        
        // update the keyboard last (it clears justPressed, and we need it!)
        this.keys.update();        
    }
    
    void render()
    {
        this.state.render();
    }
    
    void portrait()  { setOrientation(true); }
    void landscape() { setOrientation(false); }    
    
    private void setOrientation(boolean usePortrait)
    {
        if (! CONFIG_ANDROID) return;
        
        // use reflection so the standard compiler doesn't gripe
        // about PORTRAIT and LANDSCAPE
        try
        {
            Class thisClass = this.getClass();
            thisClass.getMethod("orientation").invoke(
                thisClass.getField(usePortrait ? "PORTRAIT" : "LANDSCAPE")
            );
            
        } 
        // Overly broad, but failing here is the general case.
        catch (Exception e)
        {
            println("Error in setOrientation:" + e);
        }
    }

    boolean _isAndroid()
    {
        Class _androidClass = null;
        try { 
            _androidClass = Class.forName("android.os.Build"); 
        }
        catch (ClassNotFoundException e)
        {
        }
        return (_androidClass != null);
    }
    

}

// !! Normally, I'd use a static class for this, but all
//    our code has to end up in one file for studio sketchpad,
//    and static classes have to be top level in processing.
GameClass Game = new GameClass();





// This figures out which runtime we're using dynamically,
// so we can do conditional compilation.
final String RUNTIME_ANDROID = "ANDROID";
final String RUNTIME_JVM = "JVM";
final String RUNTIME_PJS = "PJS";
 
final boolean CONFIG_PJS = (new Object()).toString() == "[object Object]";
final boolean CONFIG_ANDROID = !CONFIG_PJS && Game._isAndroid();
final boolean CONFIG_JVM = ! (CONFIG_PJS || CONFIG_ANDROID);
final boolean CONFIG_ANY_JAVA = CONFIG_ANDROID || CONFIG_JVM;
final String RUNTIME = 
    CONFIG_PJS ? RUNTIME_PJS
               : CONFIG_ANDROID ? RUNTIME_ANDROID
                                : RUNTIME_JVM;


// [ GameBasic.pde ]:

/*
 * Base class for pretty much anything inside a game.
 *
 */
class GameBasic
{
    // these are straight out of flixel
    public boolean visible = true; // if false: GameGroup won't ask it to render()
    public boolean active = true;  // if false: GameGroup won't ask it to update()
    public boolean exists = true;  // if false: GameGroup won't ask for either.
    public boolean alive = true;   // this one is just handy in games
    
    // !! these are handy for storing any of our objects in a grid:
    public int gx;
    public int gy;

    // !! this is each() for all the "single object" subclasses under GameObject
    private ArrayList<GameBasic> mJustMe = new ArrayList<GameBasic>(1);
    GameBasic()
    {
        mJustMe.add(this);
    }
     
    // these two are the core interface for all our objects:
    void update() { }
    void render() { }

    
    /**
     * each() is a special method we provide so that we have
     * a single unified interface for dealing with groups, 
     * and grids, and single objects in our game.
     *
     * GameContainer uses it to implement several variations
     * of the Visitor design pattern.
     */
    public Iterable<GameBasic> each()
    { 
        return mJustMe;
    }
}


// [ GameContainer.pde ]:

// !! An interface defines a set of methods that a class can implement.
//    These interfaces require only one method each, and are used for
//    looping through the contents of the various GameContainers 
//    in a uniform way.

// These work with all subclasses of GameContainer (GameGroup, GameState, GameGrid... GameQuadtree? )
interface GameVisitor   { public  void        visit   (GameBasic gab); }
interface GameChecker   { public  boolean     check   (GameBasic gab); }
interface GameChanger   { public  GameBasic   change  (GameBasic gab); }
interface GameExtractor { public  Object      extract (GameBasic gab); }
interface GamePopulator { public  GameBasic   populate(Object k); }
  
// The ones below add the gx and gxy parameters, which often come in handy when dealing with grids.
interface GameGridVisitor   { public void        visitCell   (int gx, int gy, GameBasic gab); }
interface GameGridChecker   { public boolean     checkCell   (int gx, int gy, GameBasic gab); }
interface GameGridChanger   { public GameBasic   changeCell  (int gx, int gy, GameBasic old); }
interface GameGridExtractor { public GameBasic   extractCell (int gx, int gy, GameBasic old); }
interface GameGridPopulator { public GameBasic   populateCell(int gx, int gy); }


// !! Abstract classes are somewhere between classes and interfaces.
//    You can't instantiate it directly, but you can subclass them
//    and you can write both regular methods with bodies and abstract
//    methods that must be overridden.

// !! using "abstract class" prevents the class from being instantiated.
//    It's perfectly okay to declare a variable of this type:
//            GameContainer gc;
//    But if you try to instantiate it (with the "new" keyword), the
//    compiler will complain.
//           GameContainer gc = new GameContainer();
abstract class GameContainer extends GameBasic
{
    // !! Here, using "abstract" in front of a method forces every subclass
    //    to override the method. If you subclass GameContainer and don't
    //    override these, the compiler will give you an error.
    abstract public ArrayList<GameBasic> each();
    abstract protected ArrayList<Object> keys();
    abstract protected void putItem(Object k, GameBasic gab);
    abstract protected GameBasic getItem(Object k);
    abstract protected GameContainer emptyCopy();
    
    // !! GameBasic defines each() for the GameObject side of the class tree.
    //    Each subclass on the GameContainer side of the tree is different,
    //    though, and will need to define its own looping constructs.
    
    // !! The benefit of forcing every class to define each() is  that we can 
    //    use the same collection methods whether we're dealing with GameGrid,
    //    GameGroup, or whatever we make later. (GameQuadTree maybe?)

    /**
     * Walk the flattened tree of FlxBasic instances provided by each();
     * 
     *  Example usage:
     *
           void census()
           {
                GameGrid grid = new GameGrid(10, 10);
                
                // grid.visit is the method defined here.
                grid.visit(new GameVisitor ()
                {
                     int count = 0;
                     
                     // !! the visit here implements the GameVisitor interface
                     public void visit(GameBasic gab)
                     {
                         count += 1;
                         println("GameBasic instance number " + count + " is "
                             + (gab.active  ? "alive" : "dead") + " and "
                             + (gab.visible ? "visible" : "invisible") + "!");
                     }
                }); // !! note the end paren and semicolon, capping off our call to grid.visit();
           }
    */
    public void visit(GameVisitor vis)
    {
        for (GameBasic gab : this.each())
        {
            vis.visit(gab);
        }
    }
    
    public GameBasic firstWhere(GameChecker chk) 
    {
        for (GameBasic gab : this.each())
        {
            if (chk.check(gab)) return gab;
        }
        return GameNull;
    }
    
    public int countWhere(GameChecker chk)
    {
        int count = 0;
        for (GameBasic gab : this.each())
        {
            if (chk.check(gab)) count++;
        }
        return count;
    }
    
    /**
     * This returns a new ArrayList, hopefully leaving
     * the current container intact.
     */
    public ArrayList<GameBasic> eachChanged(GameChanger chg)
    {
        ArrayList<GameBasic> res = new ArrayList<GameBasic>();
        for (GameBasic gab : this.each())
        {
            res.add(chg.change(gab));
        }
        return res;
    }
    
    
    /**
     * This is like eachChanged, but it returns an instance of its own class.
     * That is, GameGrid.changed() returns a GameGrid, and GameGroup.changed()
     * returns a GameGroup()
     *
     * Since we've already got abstract methods called getItem, putItem and keys,
     * we can write a generic implementation:
     */
    public GameContainer changed(GameChanger chg)
    {
        GameContainer res = this.emptyCopy();
        for (Object k : this.keys())
        {
            res.putItem(k, chg.change(this.getItem(k)));
        }
        return res;
    }
    

    /**
     * This one is almost the same as .eachChanged, but returns an ArrayList<Object>
     * instead of an ArrayList<GameBasic>. GameExtractor.extract() can
     * return any Object whatsoever.
     */
     public ArrayList extract(GameExtractor ext)
     {
        ArrayList res = new ArrayList();
        for (GameBasic gab : this.each())
        {
            res.add(ext.extract(gab));
        }
        return res;
     }
    
    
    public void populate(GamePopulator pop)
    {
        for (Object k : this.keys())
        {
            this.putItem(k, pop.populate(k));
        }
    }
    
    
    public void render()
    {
        for (GameBasic gab : this.each())
        {
            gab.render();
        }
    }
    
}


// [ GameDroid.pde ]:



// [ GameGroup.pde ]:

class GameGroup extends GameBasic
{
    ArrayList members = new ArrayList();

    GameGroup()
    {
        super();
    }
    
    GameBasic get(int i)
    {
        return (GameBasic) this.members.get(i);
    }
    
    GameBasic put(int i, GameBasic obj)
    {
        this.members.set(i, obj);
        return obj;
    }

    // for javaphiles:
    GameBasic set(int i, GameBasic obj)
    {
        return this.put(i, obj);
    }
    
    int size()
    {
        return this.members.size();
    }

    GameBasic add(GameBasic obj)
    {
        this.members.add(obj);
        return obj;
    }
    
    void remove(GameBasic obj)
    {
        this.members.remove(obj);
    }
    
    
    boolean isEmpty()
    {
        return this.size() == 0;
    }
    
    GameBasic atRandom()
    {
        if (this.isEmpty()) return null;
        return this.get((int) random(this.size()));
    }
    
    void update()
    {
        GameBasic obj;
        int len = this.members.size();
        for (int i = 0; i < len; ++i)
        {
            obj = this.get(i);
            if (obj.exists && obj.active) obj.update();
        }
    }
    
    void render()
    {
        GameBasic obj;
        int len = this.members.size();
        for (int i = 0; i < len; ++i)
        {
            obj = this.get(i);
            if (obj.exists && obj.visible) obj.render();
        }
    }
    
    void overlap(GameGroup other)
    {
        // !! this will certainly crash if the group contains other groups
        // TODO: see how flixel handles GameObject in .overlap()
        // !! meanwhile, just don't use overlap() on nested groups.
        
        int len = this.members.size();
        for (int i = 0; i < len; ++i)
        {
            GameObject a = (GameObject) this.get(i);
            if (a.active && a.exists)
            {
                for (int j = 0; j < other.size(); ++j)
                {
                    GameObject b = (GameObject) other.get(j);
                    if (b.active && b.exists && a != b && a.overlaps(b))
                    {
                        a.onOverlap(b);
                    }
                }
            }
        }
    }
    
    
    GameBasic firstDead()
    {
        int len = this.members.size();
        for (int i = 0; i < len; ++i)
        {
            GameBasic obj = this.get(i);
            if (! obj.alive) return obj;
        }
        return null;
    }
    
    GameBasic firstAlive()
    {
        int len = this.members.size();
        for (int i = 0; i < len; ++i)
        {
            GameBasic obj = this.get(i);
            if (obj.alive) return obj;
        }
        return null;
    }

    GameBasic firstInactive()
    {
        int len = this.members.size();
        for (int i = 0; i < len; ++i)
        {
            GameBasic obj = this.get(i);
            if (! obj.active) return obj;
        }
        return null;
    }


    void removeDead()
    {
        while (true)
        {
            GameBasic body = firstDead();
            if (body == null) { break; } else { remove(body); }
        }
    }

}


// [ GameGrid.pde ]:

/**
 * A 2D container, handy for tile maps, puzzle games, etc.
 */
class GameGrid extends GameContainer
{
    public final int rows;
    public final int cols;
    public final int area;
    
    private final GameBasic[] mData;
    
    GameGrid(int cols, int rows)
    {
        this.cols = cols;
        this.rows = rows;
        this.area = cols * rows;
        mData = new GameBasic[this.area];
        this.clear();
    }
    
    
    // !! We have to override the abstract methods,
    //    each() and changed() or it won't compile.
    
    public ArrayList<GameBasic> each()
    {
        ArrayList<GameBasic> these = new ArrayList<GameBasic>();
        for (int i = 0; i < this.area; ++i)
        {
            these.add(mData[i]);
        }
        return these;
    }
    
    public GameGrid changed(GameChanger chg)
    {
        GameGrid res = new GameGrid(this.cols, this.rows);
        for (int i = 0; i < this.area; ++i)
        {
            res.mData[i] = chg.change(mData[i]);
        }
        return res;
    }
    
    protected ArrayList keys()
    {
        ArrayList res = new ArrayList();
        for (int i = 0; i < this.area; i++)
        {
            res.add(i);
        }
        return res;
    }
    
    protected void putItem(Object k, GameBasic gab)
    {
        mData[(Integer) k] = gab;
    }
    
    protected GameBasic getItem(Object k)
    {
        return mData[(Integer) k];
    }
    
    protected GameContainer emptyCopy()
    {
        return new GameGrid(this.cols, this.rows);
    }
    
     
     
    /**
     * This is the 2d analog of GameGroup.get(i).
     */  
    public GameBasic get(int gx, int gy)
    {
        return mData[this.toIndex(gx, gy)];
    }
    
    /**
     * This is the 2d analog of GameGroup.put(i).
     */
    public GameBasic put(int gx, int gy, GameBasic gab)
    {
        gab.gx = gx;
        gab.gy = gy;
        mData[this.toIndex(gx, gy)] = gab;
        return gab;
    }


    // Fill the grid with NullObjects    
    public void clear()
    {
        for (int i = 0; i < mData.length; ++i)
        {
            mData[i] = GameNull;
        }
    }
    
    
    public void visitCells(GameGridVisitor vis)
    {
        for (int gx = 0; gx < this.cols; ++gx)
        {
            for (int gy = 0; gy < this.rows; ++gy)
            {
                vis.visitCell(gx, gy, this.get(gx, gy));
            }
        }
    }
    
    public void populateCells(GameGridPopulator pop)
    {
        for (int gx = 0; gx < this.cols; ++gx)
        {
            for (int gy = 0; gy < this.rows; ++gy)
            {
                this.put(gx, gy, pop.populateCell(gx, gy));
            }
        }
    }
    
    
    public void layout(final float x, final float y, final float cellW, final float cellH)
    {
        this.visitCells(new GameGridVisitor()
        { 
             public void visitCell(int gx, int gy, GameBasic gab)
             {
                  // only GameObject and its children have coordinates
                  if (gab instanceof GameObject)
                  {
                      ((GameObject) gab).reset(x + cellW * gx, y + cellH * gy, cellW, cellH);
                  }
             }
        });
    }
    
    public void fitToScreen()
    {
        this.layout(0, 0, width/this.cols, height/this.rows);
    }
    
    
    private int toIndex(int x, int y)
    {
        return this.cols * y + x;
    }
}


// [ GameKeys.pde ]:


class GameKeys
{
    HashMap mPressedKeys = new HashMap();
    HashMap mJustPressed = new HashMap();
    
    void update()
    {
        // Important: clear out the justPressed after every frame
        // (else it won't unset until they key repeats)
        mJustPressed.clear();
    }

    // keyCode is an int, but key is a char, so we need a version for each:
    boolean isKeyDown(char code) { return mPressedKeys.get(code) != null; }
    boolean isKeyDown(int  code) { return mPressedKeys.get(code) != null; }
    boolean justPressed(char code) { return mJustPressed.get(code) != null; }
    boolean justPressed(int  code) { return mJustPressed.get(code) != null; }

    void setKeyDown(Object value)
    { 
        if (key == CODED)
        {
            mJustPressed.put(keyCode, isKeyDown(keyCode) ? null : true);
            mPressedKeys.put(keyCode, value);
        }
        else
        {
            mJustPressed.put(key, isKeyDown(key) ? null : true);
            mPressedKeys.put(key, value);
        }
    }
    
    // unfortunately, we can't mix them in the array in processing
    boolean isAnyDown(char[] keys)
    {
        for (int i = 0; i < keys.length; ++i)
        {
            if (isKeyDown(keys[i])) return true;
        }
        return false;
    }
    
    boolean goN() { return isKeyDown(UP)    || isAnyDown(WASD_N); }
    boolean goW() { return isKeyDown(LEFT)  || isAnyDown(WASD_W); }
    boolean goS() { return isKeyDown(DOWN)  || isAnyDown(WASD_S); }
    boolean goE() { return isKeyDown(RIGHT) || isAnyDown(WASD_E); }
}




// [ GameMath.pde ]:


// again, virtual "static" class for studio.sketchpad
GameMathClass GameMath = new GameMathClass();
class GameMathClass
{
    float clamp(float value, float minValue, float maxValue)
    {
        if (value > maxValue) return maxValue;
        if (value < minValue) return minValue;
        return value;
    }
}

// [ GameNull.pde ]:

class _GameNull extends GameBasic
{
    public final boolean alive = false;
    public final boolean exists = false;
    public final boolean visible = false;
    public final boolean active = false;
    _GameNull()
    {
    }
    
    private final  ArrayList<GameBasic> mEmptyList = new ArrayList<GameBasic>(0);
    
    ArrayList<GameBasic> each()
    {
        return mEmptyList;
    }
}
_GameNull GameNull = new _GameNull();


// [ GameLink.pde ]:

/**
 * A GameText that looks and acts like a hyperlink.
 *
 * For the time being, hook up the mouse handling yourself
 * in your GameState, using something like this:
 * 
 *  void mouseMoved()
 *  {
 *      mLink.hover = mLink.bounds.containsPoint(mouseX, mouseY);
 *      cursor( mLink.hover ? HAND : ARROW );
 *  }
 *  
 *  void mousePressed()
 *  {
 *      if (mLink.hover) mLink.click();
 *  }
 * 
 */
public class GameLink extends GameText
{
    String url;
    
    // !! this is meant to be changed from outside
    public boolean hover = false;
  
    GameLink(String label, String url, float x, float y, color textColor, int fontSize)
    {
        super(label, x, y, textColor, fontSize);
        this.url = url;
        this.calcBounds();
    }
    
    public void render()
    {
        if (this.bounds.visible) this.bounds.render();
        super.render();
        
        if (hover)
        {
            stroke(this.textColor);
            float txtW = textWidth(this.label);
            // TODO: get underline working when align != CENTER, BASELINE
            float lineY = this.y +2;
            line(this.x - txtW/2, lineY, this.x + txtW/2 , lineY);
        }
        
    }
    
    public void click()
    {
        link(this.url, "_new");
    }
    
}


// [ GameObject.pde ]:

/*
 *  This class represents a GameObject, occupying a
 *  rectangular area in space.
 */
class GameObject extends GameBasic
{
    public float x = 0;
    public float y = 0;
    public float w = 0;
    public float h = 0;

    float dx = 0;
    float dy = 0;
    float health = 1;
    
    GameObject()
    {
        reset(0,0,0,0);
    }
    
    GameObject(float x, float y, float w, float h)
    {
        reset(x, y, w, h);
    }
    
    public void reset(float x, float y, float w, float h)
    {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }
    
    public float x2()
    {
        return this.x + this.w;
    }
    
    public float y2()
    {
        return this.y + this.h;
    }
    
    public boolean containsPoint(float x, float y)
    {
        return this.x <= x && x <= this.x2()
            && this.y <= y && y <= this.y2();
    }
    
    // http://stackoverflow.com/questions/306316/determine-if-two-Squares-overlap-each-other
    public boolean overlaps(GameObject that)
    {
        return (this.x < that.x2() && this.x2() > that.x &&
                this.y < that.y2() && this.y2() > that.y);
    }

    public boolean contains(GameObject that)
    {
        return this.x <= that.x
            && this.y <= that.y
            && this.x2() >= that.x2()
            && this.y2() >= that.y2();
    }
    
    public void update()
    {
        x += dx;
        y += dy;
    }
    
    public void hurt()
    {
        this.health--;
        if (this.health <= 0)
        {
            this.health = 0;
            this.alive = false;
            this.onDeath();
        }
    }
    
    public void onDeath()
    {
        this.visible = false;
    }
    
    public void onOverlap(GameObject other)
    {
    }
    
}


// [ GameRect.pde ]:

/**
 * A simple rectangle that draws itself on screen.
 * Will toggle fill colors based on this.alive.
 */
class GameRect extends GameObject
{
    color liveColor = #FFFFFF;
    color deadColor = #CCCCCC;
    color lineColor = #666666;
    int lineWeight = 1;
  
    GameRect()
    {
        super(0, 0, 0, 0);
    }
  
    GameRect (float x, float y, float w, float h)
    {
        super(x, y, w, h);
    }
    
    public void render()
    {
        strokeWeight(this.lineWeight);
        fill(this.alive ? this.liveColor : this.deadColor );
        rect(this.x, this.y, this.w, this.h);
    }  
}


// [ GameSheet.pde ]:

class GameSheet
{
    String assetPath; // empty means the "data" directory
    PImage sheet;
    ArrayList frames = new ArrayList();
    
    GameSheet(String imageName, int cellW, int cellH, String jsPath)
    {
        this.assetPath = CONFIG_PJS ? jsPath : "";
        this.sheet = loadImage(this.assetPath + "/" + imageName);
        
        for (int y = 0; y < this.sheet.height; y += cellH)
        {
            for (int x = 0; x < this.sheet.width; x += cellW)
            {
                PImage cell = createImage(cellW, cellH, ARGB);
                cell.copy(sheet, x, y, cellW, cellH, 0, 0, cellW, cellH);
                this.frames.add(cell);
            }
        }
    }
    
    PImage getFrame(int i)
    {
        return (PImage) this.frames.get(i);
    }
    
    PImage[] getFrames(int[] wantedFrames)
    {
        PImage[] res = new PImage[wantedFrames.length];
        for (int i = 0; i < wantedFrames.length; ++i)
        {
            res[i] = this.getFrame(wantedFrames[i]);
        }
        return res;
    }
    
    // for debugging:
    void renderAll(int x, int y, int perRow)
    {
        int gx, gy;
        for (int i = 0; i < this.frames.size(); ++i)
        {
            PImage img = this.getFrame(i);
            gy = (int) i / perRow;
            gx = i % perRow;
            image(img, x + gx * img.width, y + gy * img.height);
            stroke(255);
            noFill();
            rect(x + gx * img.width, y + gy * img.height, img.width, img.width);
        }
    }
}

// [ GameSprite.pde ]:

class GameSprite extends GameObject
{
    // animation stuff:
    int frame = 0;
    private PImage[] mFrames;
    boolean animated = false;
    GameTimer timer = new GameTimer(SECONDS/8);

    // rotation stuff:
    int degrees = 0;    
    float xOffset = 0;
    float yOffset = 0;
    GameSprite(float x, float y)
    {
        super(x, y, 0, 0);
        animated = true;
    }
    
    void setFrames(PImage[] frames)
    {
        mFrames = frames;
        sizeToFrame();
    }
    
    void sheetFrames(int[] frameNums)
    {
        this.setFrames(SHEET.getFrames(frameNums));
    }
    
    
    void sizeToFrame()
    {
        this.w = mFrames[this.frame].width;
        this.h = mFrames[this.frame].height;
        this.xOffset = this.w / 2;
        this.yOffset = this.h / 2;
    }
    
    void update()
    {
        if (this.animated)
        {
            this.timer.update();
            if (this.timer.ready)
            {
                this.frame++;
                this.frame %= mFrames.length;
            }
        }
    }
    
    void randomize()
    {
        this.frame = (int) random(mFrames.length);
        this.timer.randomize();
    }
    
    void render()
    {
        if (! visible) return;
        
        if (this.degrees == 0)
        {
            image(mFrames[this.frame], this.x, this.y);
        }
        else
        {
            pushMatrix();
            translate(this.x + this.xOffset, this.y + this.yOffset);
            rotate(this.degrees * PI / 180);
            image(mFrames[this.frame], -this.xOffset, -this.yOffset);
            popMatrix();
        }
        
        if (DEBUG)
        {
          stroke(255);
          noFill();
          rect(x, y, w, h);
        }
    }
}

// [ GameSquare.pde ]:


class GameSquare extends GameRect
{
    GameSquare (float x, float y, float side)
    {
        super(x, y, side, side);
    }
}


// [ GameState.pde ]:

class GameState extends GameGroup
{
    color bgColor = #000000;
    
    void create()
    {
    }
    
    void render()
    {
        background(bgColor);
        super.render();
    }
    
    // empty event handlers:
    void mousePressed() { }
    void mouseReleased() { }
    void mouseDragged()  { }
    void mouseMoved() { }
    void keyPressed() { } 
    void keyReleased() { }
}


// [ GameText.pde ]:

/**
 * A GameObject that displays text.
 *
 */
class GameText extends GameObject
{
    public String label;
    public color textColor;
    public int fontSize = 20;
    public int align = CENTER;
    public int vAlign = BASELINE;
    
    /**
     * A GameRect in case you want to draw a background or border.
     * It's hidden by default.
     * If you change .x, .y, or .label, you should call calcBounds();
     */
    public GameRect bounds;
    
    GameText(String label, float x, float y, color textColor, int fontSize)
    {
        super(x, y, 0, 0);
        
        this.fontSize = fontSize;
        this.textColor = textColor;
        
        this.setLabel(label);
    }
    
    public void render()
    {
        this.setupFont();
        text(this.label, this.x, this.y);
    }

    public void setLabel(String label)
    {
       this.label = label;   
       this.calcBounds();
    }
    
    
    public void calcBounds()
    {
        this.setupFont();
        
        this.w = textWidth(this.label);
        this.h = textAscent() + textDescent();
        
        this.bounds = new GameRect(this.x, this.y, this.w, this.h);
        this.bounds.liveColor = #000000;
        this.bounds.lineColor = #999999;
        this.bounds.visible = false;
        
        if (this.align == CENTER)
            this.bounds.x -= this.w / 2;
        if (this.vAlign == BASELINE)
            this.bounds.y -= textAscent();
    }

    
    protected void setupFont()
    {
        fill(this.textColor);
        textFont(Game.defaultFont, this.fontSize);
        textAlign(this.align);
    }
    
}


// [ GameTimer.pde ]:


/*
 * GameTimer timer = new GameTimer(1.25 * SECONDS);
 *
 * Then in your .update() method:
 *
 *     timer.update();
 *     if (timer.ready) { }
 *
 * Alternatively, you can override onTick();
 *
 */
class GameTimer extends GameObject
{
    float millisPerTick = 0;
    float mMillisSoFar = 0;
    boolean ready;
    int tickCount = 0;
    
    GameTimer(float millisPerTick)
    {
        super(0, 0, 0, 0);
        this.millisPerTick = millisPerTick;
    }
    
    GameTimer(float millisPerTick, boolean immediate)
    {
        super(0, 0, 0, 0);
        this.millisPerTick = millisPerTick;
        if (immediate) mMillisSoFar = millisPerTick;
    }

    void setTicksPerSecond(float ps)
    {
        this.millisPerTick = SECONDS / ps;
    }
    
    void update()
    {
        ready = false;
        mMillisSoFar += Game.frameMillis;
        
        if (mMillisSoFar > millisPerTick)
        {
            ready = true;
            mMillisSoFar = 0;
            tickCount++;
            onTick();
        }
    }
    
    // convenience so we don't have to keep calling .update()
    boolean checkReady()
    {
        update();
        return ready;
    }
    
    // this just adds a little variety if you have a
    // bunch of objects doing the same thing.
    void randomize()
    {
        mMillisSoFar = random(millisPerTick);
    }
    
    void onTick()
    {
    }
}

/* -- [End GameSketchLib] --------------------------------------*/