/**
* Copyright devon_o ( http://wonderfl.net/user/devon_o )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/p7Hv
*/
package
{
import com.bit101.components.PushButton;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.utils.setTimeout;
import starling.core.Starling;
import starling.display.Sprite;
import starling.text.BitmapFont;
import starling.text.TextField;
/**
* Click gems with at least one neighbor of same color...
* @author Devon O.
*/
[SWF(width='465', height='465', backgroundColor='#000000', frameRate='60')]
public class Main extends flash.display.Sprite
{
private var sRoot:starling.display.Sprite;
private var game:GameBoard;
private var scoreText:TextField;
private var newGameButton:PushButton;
public function Main():void
{
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void
{
removeEventListener(Event.ADDED_TO_STAGE, init);
initStarling();
}
private function initStarling():void
{
var s:Starling = new Starling(starling.display.Sprite, stage);
s.addEventListener("rootCreated", onStarlingRoot);
s.start();
}
private function onStarlingRoot(e:*):void
{
e.currentTarget.removeEventListener("rootCreated", onStarlingRoot);
sRoot = e.currentTarget.root;
this.scoreText = new TextField(100, 25, "Score: 0", BitmapFont.MINI, BitmapFont.NATIVE_SIZE, 0xFFFFFF);
sRoot.addChild(this.scoreText);
this.game = new GameBoard(12, 12, 12 * 12);
this.game.x = (stage.stageWidth - this.game.width) >> 1;
this.game.y = (stage.stageHeight - this.game.height) >> 1;
this.game.addEventListener("gameOver", onGameOver);
sRoot.addChild(this.game);
this.newGameButton = new PushButton(this, this.game.x, 435, "New Game", startGame);
addEventListener(Event.ENTER_FRAME, onTick);
}
private function onTick(e:Event):void
{
this.game.update();
this.scoreText.text = "Score: " + String(this.game.getScore());
}
private function onGameOver(e:*):void
{
this.newGameButton.enabled = true;
}
private function startGame(e:MouseEvent):void
{
this.newGameButton.enabled = false;
this.game.newBoard();
}
}
}
import flash.display.BitmapData;
import flash.display.GradientType;
import flash.display.InterpolationMethod;
import flash.display.Shape;
import flash.display.SpreadMethod;
import flash.events.SampleDataEvent;
import flash.filters.GlowFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.media.Sound;
import flash.utils.getTimer;
import flash.utils.setTimeout;
import starling.display.Image;
import starling.display.Sprite;
import starling.events.Event;
import starling.events.Touch;
import starling.events.TouchEvent;
import starling.events.TouchPhase;
import starling.textures.Texture;
class Gem extends Image
{
// For hints
private static const NUM_FLASHES:int = 4;
private static const FLASH_TIME:int = 150;
public var tx:Number;
public var ty:Number;
private var mChosen:Boolean;
private var mColor:uint;
private var mPosition:Point;
private var mTexture:Texture;
private var mIsInteractive:Boolean;
private var mNeighbors:Array;
private var mActive:Boolean;
private var mCurrentFlash:int;
public function Gem(color:uint, xpos:int = 0, ypos:int = 0, isInteractive:Boolean = false)
{
mChosen = false;
mColor = color;
mPosition = new Point(xpos, ypos);
mIsInteractive = isInteractive;
mTexture = createTexture();
super(mTexture);
this.color = mColor;
if (isInteractive)
{
visible = false;
mActive = false;
addEventListener(TouchEvent.TOUCH, onTouch);
}
}
override public function dispose():void
{
super.dispose();
mTexture = null;
removeEventListener(TouchEvent.TOUCH, onTouch);
}
public function appear():void
{
visible = true;
}
public function hint():void
{
mCurrentFlash = 0;
onHint();
}
private function onHint():void
{
color = 0xFFFFFF;
setTimeout(offHint, FLASH_TIME);
}
private function offHint():void
{
color = mColor;
if (++mCurrentFlash < NUM_FLASHES)
{
setTimeout(onHint, FLASH_TIME);
}
}
private function onTouch(event:TouchEvent):void
{
if (!mActive) return;
var t:Touch = event.getTouch(this);
if (!t) return;
if (t.phase == TouchPhase.BEGAN)
{
this.mIsInteractive = false;
dispatchEventWith("click", false, this);
}
}
public function get position():Point { return mPosition; }
public function set position(value:Point):void { mPosition = value; }
public function get gemColor():uint { return mColor; }
public function get chosen():Boolean { return mChosen; }
public function set chosen(value:Boolean):void { mChosen = value; }
public function set neighbors(value:Array):void { mNeighbors = value; }
public function get neighbors():Array { return mNeighbors; }
public function get active():Boolean { return mActive; }
public function set active(value:Boolean):void { mActive = value; }
public function toString():String
{
return "[Gem position={" + mPosition.x + ", " + mPosition.y + "} color=0x" + mColor.toString(16) + "]";
}
private function createTexture():Texture
{
const size:int = 32;
var colors:Array = [0xafadad, 0x444444];
var alphas:Array = [1, 1];
var ratios:Array = [0, 255];
var matrix:Matrix = new Matrix();
matrix.createGradientBox(size, size, 0, -8, -8);
var s:Shape = new Shape();
s.graphics.lineStyle(2, 0x000000);
s.graphics.beginGradientFill(GradientType.RADIAL, colors, alphas, ratios, matrix);
s.graphics.drawRect(0, 0, size, size);
s.filters = [new GlowFilter(0x000000, 1, 4, 4, 4, 3, true)];
var dat:BitmapData = new BitmapData(size, size, false, 0x0);
dat.draw(s);
var t:Texture = Texture.fromBitmapData(dat);
dat.dispose();
s.graphics.clear();
return t;
}
}
class GameBoard extends Sprite
{
public static const STATE_NULL:int = -1;
public static const STATE_INIT:int = 0;
public static const STATE_APPEAR:int = 1;
public static const STATE_DROP:int = 2;
public static const STATE_PLAY:int = 3;
public static const STATE_REMOVE:int = 4;
public static const STATE_COMPLETE:int = 5;
public static const COLORS:Vector.<uint> = new <uint>[0xFF0000, 0x00FF00, 0x0000FF, 0xFFFF00, 0x00FFFF];
private static const HINT_TIME:int = 6000;
private var mState:int;
private var mCurrentGem:int; // used for appearing / disappearing
private var mTimer:uint;
private var mGemsToRemove:Array;
private var mNumGems:int;
private var mRows:int;
private var mColumns:int;
private var mGems:Vector.<Gem>;
private var mBack:Sprite;
private var mScore:int;
private var mTimeSinceLastClick:int;
public function GameBoard(rows:int, columns:int, numGems:int)
{
if (rows * columns != numGems)
throw new Error("Instantiated GameBoard with incorrect number of rows, columns or gems!");
mRows = rows;
mColumns = columns;
mNumGems = numGems;
mGems = new Vector.<Gem>();
mScore = 0;
setState(STATE_INIT);
newBack();
}
/** Starts a new game */
public function newBoard():void
{
mScore = 0;
removeGems();
var col:int, row:int, i:int;
var colors:Array = getDistribution();
// create gems
for (i = 0; i < mNumGems; i++)
{
col = i % mColumns;
row = int(i / mColumns);
var gem:Gem = new Gem(colors[i], col, row, true);
gem.addEventListener("click", onGemClick);
gem.x = gem.tx = col * 32;
gem.y = gem.ty = row * 32;
mGems.push(gem);
addChild(gem);
}
updateData();
setState(STATE_APPEAR);
}
/** get current score */
public function getScore():int
{
return mScore;
}
/** clean up */
override public function dispose():void
{
super.dispose();
removeGems();
removeBack();
}
/** clean up the backing graphic */
private function removeBack():void
{
mBack.unflatten();
while (mBack.numChildren)
{
var g:Gem = mBack.getChildAt(0) as Gem;
mBack.removeChild(g);
g.dispose();
g = null;
}
removeChild(mBack);
mBack.dispose();
mBack = null;
}
/** Get array of colors for each number of gems */
private function getDistribution():Array
{
var len:int = COLORS.length
var numEach:int = Math.ceil(mNumGems / len);
var ret:Array = [];
for (var i:int = 0; i < len; i++)
{
for (var j:int = 0; j < numEach; j++)
{
ret.push(COLORS[i]);
}
}
return shuffleArray(ret);
}
/** Shuffle an array */
private function shuffleArray(arr:Array):Array
{
var len:int = arr.length;
var temp:*;
for (var i:int = 0; i < len; i ++) {
var rand:int = Math.floor (Math.random() * len);
temp = arr[i];
arr[i] = arr[rand];
arr[rand] = temp;
}
return arr;
}
/** when clicking on gem */
private function onGemClick(event:Event):void
{
var gem:Gem = event.data as Gem;
var neighbors:Array = gem.neighbors;
var len:int = neighbors.length;
// if there are any gems to remove
if (len > 1)
{
setGemsActive(false);
mGemsToRemove = neighbors;
mScore += len * 50; // higher score for larger removes
mTimeSinceLastClick = getTimer();
setState(STATE_REMOVE);
}
}
/** Get a gem by its position */
private function getGemByPos(pos:Point):Gem
{
var i:int = mGems.length;
while (i--)
{
var gem:Gem = mGems[i];
if (gem.position.x == pos.x && gem.position.y == pos.y)
return gem;
}
return null;
}
/** Creates the 'back' object that sits behind the gems */
private function newBack():void
{
if (mBack)
{
mBack.removeChildren();
removeChild(mBack);
mBack = null;
}
mBack = new Sprite();
var col:int, row:int, i:int;
// create empty slots
for (i = 0; i < mNumGems; i++)
{
col = i % mColumns;
row = int(i / mColumns);
var emptySlot:Gem = new Gem(0x121212);
emptySlot.x = col * 32;
emptySlot.y = row * 32;
mBack.addChild(emptySlot);
}
mBack.touchable = false;
mBack.flatten();
addChild(mBack);
}
/** flash the largest group */
private function showHints():void
{
mTimeSinceLastClick = getTimer();
// Get best move
var largestPlay:int = 0;
var play:Array;
var i:int = mGems.length;
var g:Gem;
while (i--)
{
g = mGems[i];
var len:int = g.neighbors.length;
if (len > largestPlay)
{
largestPlay = len;
play = g.neighbors;
}
}
// show the hint
i = play.length;
while (i--)
{
g = play[i] as Gem;
g.hint();
}
}
/** set game state */
private function setState(state:int):void
{
mTimer = 0;
mCurrentGem = 0;
mState = state;
}
/** make gems clickable (or not) */
private function setGemsActive(value:Boolean):void
{
var i:int = mGems.length;
while (i--)
{
mGems[i].active = value;
}
}
/** Remove (and dispose) all gems */
private function removeGems():void
{
var i:int = mGems.length;
while (i--)
{
var g:Gem = mGems[i];
removeChild(g);
g.dispose();
g = null
}
mGems.length = 0;
}
/** Get array of neighbors of same color of specified gem */
public function getNeighbors(gem:Gem):Array
{
var ret:Array = [], q:Array = [];
var n:Gem;
var p:Point = new Point();
// Flood fill algo from Wikipedia
//Add node to the end of Q.
q.push(gem);
//While Q is not empty:
while (q.length > 0)
{
//Set n equal to the last element of Q.
//Remove last element from
n = q.pop();
//If the color of n is equal to target-color:
if (n && n.gemColor == gem.gemColor && !n.chosen)
{
//Set the color of n to replacement-color.
n.chosen = true;
ret.push(n);
//Add west node to end of Q.
p.x = n.position.x - 1;
p.y = n.position.y;
q.push(getGemByPos(p));
//Add east node to end of Q.
p.x = n.position.x + 1;
p.y = n.position.y;
q.push(getGemByPos(p));
//Add north node to end of Q.
p.x = n.position.x;
p.y = n.position.y - 1;
q.push(getGemByPos(p));
//Add south node to end of Q.
p.x = n.position.x;
p.y = n.position.y + 1;
q.push(getGemByPos(p));
}
}
return ret;
}
/** Unselect all gems */
public function unselectAll():void
{
var i:int = mGems.length;
while (i--)
{
mGems[i].chosen = false;
}
}
/** update according to state */
public function update():void
{
switch(mState)
{
case STATE_INIT :
updateInit();
break;
case STATE_APPEAR :
updateAppear();
break;
case STATE_DROP :
updateDrop();
break;
case STATE_PLAY :
updatePlay();
break;
case STATE_REMOVE :
updateRemove();
break;
case STATE_COMPLETE :
updateComplete();
break;
case STATE_NULL :
default :
updateNull();
break;
}
}
/** Updates the positions, targets and neighbors of all gems */
public function updateData():void
{
var i:int, j:int;
var p:Point = new Point(), pBelow:Point = new Point(), pLeft:Point = new Point();
var g:Gem, below:Gem, left:Gem, tmp:Gem;
// Move Down
for (i = 0; i < mColumns; i++)
{
for (j = mRows-1; j >-1 ; j--)
{
p.x = i;
p.y = j;
g = getGemByPos(p);
if (!g) continue;
pBelow = p.clone();
while(pBelow.y < mRows-1)
{
pBelow.y++;
below = getGemByPos(pBelow);
if (!below)
{
g.position = pBelow.clone();
g.ty = pBelow.y * 32;
}
}
}
}
// Move Left
for (i = 1; i < mColumns; i++)
{
p.x = i;
p.y = mRows - 1;
g = getGemByPos(p);
if (!g) continue;
pLeft = p.clone();
while (pLeft.x > 0)
{
pLeft.x --;
left = getGemByPos(pLeft);
if (!left)
{
g.position = pLeft.clone();
g.tx = pLeft.x * 32;
}
}
for (j = 0; j < mRows; j++)
{
p.y = j;
tmp = getGemByPos(p);
if (tmp)
{
tmp.position.x = g.position.x;
tmp.tx = g.tx;
}
}
}
// Get neighbors
i = mGems.length;
var gameOver:Boolean = true;
while (i--)
{
g = mGems[i];
g.neighbors = getNeighbors(g);
if (g.neighbors.length > 1) gameOver = false;
unselectAll();
}
if (gameOver)
endGame();
}
/** no state */
private function updateNull():void
{
return;
}
/** add init state if desired */
private function updateInit():void
{
return;
}
/** Update the gems appearing */
private function updateAppear():void
{
mGems[mCurrentGem].appear();
if (++mCurrentGem == mGems.length)
{
mTimeSinceLastClick = getTimer();
setGemsActive(true);
setState(STATE_PLAY);
}
}
/** update gems dropping */
private function updateDrop():void
{
var numGemsSet:int = 0;
const numGems:int = mGems.length;
var i:int = numGems;
while (i--)
{
var hset:Boolean = false;
var vset:Boolean = false;
var gem:Gem = mGems[i];
gem.x += (gem.tx - gem.x) / 1.5;
if (Math.abs(gem.x - gem.tx) < 1)
{
gem.x = gem.tx;
hset = true;
}
gem.y += (gem.ty - gem.y) / 1.5;
if (Math.abs(gem.y - gem.ty) < 1)
{
gem.y = gem.ty;
vset = true;
}
if (hset && vset)
{
numGemsSet++;
if (numGemsSet == numGems)
{
// All gems are done falling
setGemsActive(true);
setState(STATE_PLAY);
}
}
}
}
/** Update playing state */
private function updatePlay():void
{
if (getTimer() - mTimeSinceLastClick > HINT_TIME)
{
showHints();
}
}
/** Update removing state */
private function updateRemove():void
{
if (mTimer % 4 == 0)
{
var g:Gem = mGemsToRemove[mCurrentGem] as Gem;
var idx:int = mGems.indexOf(g);
mGems.splice(idx, 1);
removeChild(g);
g.dispose();
g = null;
mScore += 10;
if (++mCurrentGem == mGemsToRemove.length)
{
setTimeout(endRemove, 5);
}
}
mTimer++;
}
/** End removing of gems */
private function endRemove():void
{
unselectAll();
updateData();
setState(STATE_DROP);
}
/** Game over */
private function updateComplete():void
{
setState(STATE_NULL);
dispatchEventWith("gameOver");
}
/** End the game */
private function endGame():void
{
setTimeout(setState, 1000, STATE_COMPLETE);
}
}