Tetris
/**
* Copyright fokhei ( http://wonderfl.net/user/fokhei )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/xjsH
*/
package {
import flash.display.Sprite;
public class FlashTest extends Sprite {
private var config:Config;
private var game:Game;
private var renderer:Renderer;
public function FlashTest(){
config = new Config();
game = new Game();
game.init(config);
renderer = new Renderer();
renderer.init(config, game);
renderer.build();
addChild(renderer);
game.start();
}
}
}
/********************************************/
class Config {
public var spaceColumn:uint = 23;
public var spaceRow:uint = 23;
public var blockSize:uint = 20;
}
/********************************************/
import flash.events.Event;
class GameEvent extends Event {
static public const GAME_START:String = "GAME_START";
static public const GAME_OVER:String = "GAME_OVER";
static public const GAME_RESET:String = "GAME_RESET";
static public const MAP_UPDATE:String = "MAP_UPDATE";
public function GameEvent(type:String){
super(type);
}
}
/********************************************/
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.Sprite;
class Block {
static public var TYPE_NONE:String = "0";
static public var TYPE_I:String = "I";
static public var TYPE_J:String = "J";
static public var TYPE_L:String = "L";
static public var TYPE_Q:String = "Q";
static public var TYPE_S:String = "S";
static public var TYPE_T:String = "T";
static public var TYPE_Z:String = "Z";
static public var TYPES:Array = [Block.TYPE_I, Block.TYPE_J, Block.TYPE_L, Block.TYPE_Q, Block.TYPE_S, Block.TYPE_T, Block.TYPE_Z];
public var type:String = Block.TYPE_NONE;
public function Block(type:String = "0"){
this.type = type;
}
static public function getRandomType():String {
var len:uint = Block.TYPES.length - 1;
var r:Number = Math.round(Math.random() * len);
return Block.TYPES[r];
}
public function getView(cfg:Config):DisplayObject {
var size:uint = cfg.blockSize;
var fillColor:uint;
var fillAlpha:Number = 1;
switch (type){
case Block.TYPE_I:
fillColor = 0x00F0F0;
break;
case Block.TYPE_J:
fillColor = 0x0000F0;
break;
case Block.TYPE_L:
fillColor = 0xF0A000;
break;
case Block.TYPE_Q:
fillColor = 0xF0F000;
break;
case Block.TYPE_S:
fillColor = 0x00F000;
break;
case Block.TYPE_T:
fillColor = 0xA000F0;
break;
case Block.TYPE_Z:
fillColor = 0xD80000;
break;
default:
case Block.TYPE_NONE:
return null;
break;
}
var s:Sprite = new Sprite;
var g:Graphics = s.graphics;
g.beginFill(fillColor, fillAlpha);
g.lineStyle(1, 0x666666, fillAlpha);
g.drawRect(0, 0, size, size);
g.endFill();
return s;
}
public function toString():String {
return this.type;
}
}
/********************************************/
import flash.display.DisplayObject;
import flash.display.Sprite;
import flash.geom.Point;
class Puzzle {
public var type:String;
public var blocks:Array;
public var position:Point;
public function Puzzle(blockType:String = null){
position = new Point(0, 0);
if (blockType == null){
blockType = Block.getRandomType();
}
changeType(blockType);
}
public function rotateArray2D(arr:Array):Array {
var d1:Number = arr.length;
var d2:Number = arr[0].length;
var r:Array = new Array(d2);
for (var i:Number = 0; i < d2; i++){
var t:Array = new Array(d1);
for (var j:Number = 0; j < d1; j++){
t[j] = arr[d1 - 1 - j][i];
}
r[i] = t;
}
return r;
}
private function changeType(blockType:String):void {
this.type = blockType;
switch (type){
case Block.TYPE_I:
blocks = [[new Block(Block.TYPE_I), new Block(Block.TYPE_I), new Block(Block.TYPE_I), new Block(Block.TYPE_I)]]
break;
case Block.TYPE_J:
blocks = [[new Block(Block.TYPE_J), new Block(Block.TYPE_NONE), new Block(Block.TYPE_NONE)], [new Block(Block.TYPE_J), new Block(Block.TYPE_J), new Block(Block.TYPE_J)]]
break;
case Block.TYPE_L:
blocks = [[new Block(Block.TYPE_L), new Block(Block.TYPE_L), new Block(Block.TYPE_L)], [new Block(Block.TYPE_L), new Block(Block.TYPE_NONE), new Block(Block.TYPE_NONE)]
]
break;
case Block.TYPE_Q:
blocks = [[new Block(Block.TYPE_Q), new Block(Block.TYPE_Q)], [new Block(Block.TYPE_Q), new Block(Block.TYPE_Q)]
]
break;
case Block.TYPE_S:
blocks = [[new Block(Block.TYPE_NONE), new Block(Block.TYPE_S), new Block(Block.TYPE_S)], [new Block(Block.TYPE_S), new Block(Block.TYPE_S), new Block(Block.TYPE_NONE)]
]
break;
case Block.TYPE_T:
blocks = [[new Block(Block.TYPE_NONE), new Block(Block.TYPE_T), new Block(Block.TYPE_NONE)], [new Block(Block.TYPE_T), new Block(Block.TYPE_T), new Block(Block.TYPE_T)]
]
break;
case Block.TYPE_Z:
blocks = [[new Block(Block.TYPE_Z), new Block(Block.TYPE_Z), new Block(Block.TYPE_NONE)], [new Block(Block.TYPE_NONE), new Block(Block.TYPE_Z), new Block(Block.TYPE_Z)]
]
break;
default:
case Block.TYPE_NONE:
blocks = []
break;
}
}
public function getView(cfg:Config):DisplayObject {
var s:Sprite = new Sprite();
var block:Block;
var blockClip:DisplayObject;
var h:uint = blocks.length;
var w:uint = blocks[0].length;
var ty:uint, tx:uint;
for (ty = 0; ty < h; ty++){
for (tx = 0; tx < w; tx++){
block = blocks[ty][tx] as Block;
if (block != null){
blockClip = block.getView(cfg);
if (blockClip != null){
blockClip.x = tx * cfg.blockSize;
blockClip.y = ty * cfg.blockSize;
s.addChild(blockClip);
}
}
}
}
return s;
}
public function rotateRight():void {
blocks = rotateArray2D(blocks);
}
public function moveDown():void {
position.y++;
}
public function moveRight():void {
position.x++;
}
public function moveLeft():void {
position.x--;
}
public function allowRotateRight(game:Game):Boolean {
var clone:Array = rotateArray2D(blocks);
var h:uint = clone.length;
var w:uint = clone[0].length;
var chkBlock:Block;
var ty:int, tx:int;
var cx:int, cy:int;
for (ty = 0; ty < h; ty++){
for (tx = 0; tx < w; tx++){
if (clone[ty][tx].type == Block.TYPE_NONE){
continue;
} else {
cx = this.position.x + tx;
cy = this.position.y + ty;
if (cx < 0 || cx >= game.cfg.spaceColumn || cy < 0 || cy >= game.cfg.spaceRow - 1){
return false;
}
chkBlock = game.getBlock(cx, cy);
if (chkBlock.type != Block.TYPE_NONE){
return false;
}
}
}
}
return true;
}
public function allowMoveDown(game:Game):Boolean {
if ((position.y + blocks.length) >= game.cfg.spaceRow){
return false;
}
var h:uint = blocks.length - 1;
var w:uint = blocks[0].length;
var ty:int, tx:int;
var chkBlock:Block;
var cx:int, cy:int;
for (ty = h; ty > -1; ty--){
for (tx = 0; tx < w; tx++){
if (blocks[ty][tx].type == Block.TYPE_NONE){
continue;
} else {
cx = this.position.x + tx;
cy = this.position.y + ty + 1;
chkBlock = game.getBlock(cx, cy);
if (chkBlock.type != Block.TYPE_NONE){
return false;
}
}
}
}
return true;
}
public function allowMoveRight(game:Game):Boolean {
if ((position.x + blocks[0].length) >= game.cfg.spaceColumn){
return false;
}
var h:uint = blocks.length - 1;
var w:uint = blocks[0].length - 1;
var ty:int, tx:int;
var chkBlock:Block;
var cx:int, cy:int;
for (ty = h; ty > -1; ty--){
for (tx = w; tx > -1; tx--){
if (blocks[ty][tx].type == Block.TYPE_NONE){
continue;
} else {
cx = this.position.x + tx + 1;
cy = this.position.y + ty;
chkBlock = game.getBlock(cx, cy);
if (chkBlock.type != Block.TYPE_NONE){
return false;
}
}
}
}
return true;
}
public function allowMoveLeft(game:Game):Boolean {
if (position.x <= 0){
return false;
}
var h:uint = blocks.length - 1;
var w:uint = blocks[0].length;
var ty:int, tx:int;
var chkBlock:Block;
var cx:int, cy:int;
for (ty = h; ty > -1; ty--){
for (tx = 0; tx < w; tx++){
if (blocks[ty][tx].type == Block.TYPE_NONE){
continue;
} else {
cx = this.position.x + tx - 1;
cy = this.position.y + ty;
chkBlock = game.getBlock(cx, cy);
if (chkBlock.type != Block.TYPE_NONE){
return false;
}
}
}
}
return true;
}
public function appendToMap(game:Game):void {
var h:uint = blocks.length;
var w:uint = blocks[0].length;
var ty:int, tx:int;
var cx:int, cy:int;
var block:Block;
for (ty = 0; ty < h; ty++){
for (tx = 0; tx < w; tx++){
if (blocks[ty][tx].type == Block.TYPE_NONE){
continue;
} else {
cx = this.position.x + tx;
cy = this.position.y + ty;
if (cy >= 0){
block = game.getBlock(cx, cy) as Block;
if (block != null){
block.type = blocks[ty][tx].type;
}
}
}
}
}
}
public function allowDropDown(game:Game):Boolean {
var h:uint = blocks.length - 1;
var w:uint = blocks[0].length;
var ty:int, tx:int;
var chkBlock:Block;
var cx:int, cy:int;
for (ty = h; ty > 0; ty--){
for (tx = 0; tx < w; tx++){
if (blocks[ty][tx].type == Block.TYPE_NONE){
continue;
} else {
cx = this.position.x + tx;
cy = this.position.y + ty;
chkBlock = game.getBlock(cx, cy);
if (chkBlock.type != Block.TYPE_NONE){
return false;
}
}
}
}
return true;
}
}
/**********************************************************/
import flash.events.EventDispatcher;
import flash.events.TimerEvent;
import flash.geom.Point;
import flash.utils.Timer;
class Game extends EventDispatcher {
public var cfg:Config;
private var map:Array;
public var activePuzzle:Puzzle;
public var nextPuzzle:Puzzle;
private var updateTimer:Timer;
public function init(config:Config):void {
cfg = config;
}
public function reset():void {
updateTimer.stop();
resetMap();
activePuzzle = null;
dispatchEvent(new GameEvent(GameEvent.GAME_RESET));
start();
}
private function resetMap():void {
map = new Array();
var h:uint = cfg.spaceRow;
var w:uint = cfg.spaceColumn;
var ty:int, tx:int;
for (ty = 0; ty < h; ty++){
map[ty] = [];
for (tx = 0; tx < w; tx++){
map[ty][tx] = new Block(Block.TYPE_NONE);
}
}
for (ty = 0; ty <= -1; ty--){
for (tx = 0; tx < w; tx++){
map[ty][tx] = new Block(Block.TYPE_NONE);
}
}
}
private function newRow():Array {
var row:Array = new Array();
var w:uint = cfg.spaceColumn;
var tx:int;
for (tx = 0; tx < w; tx++){
row[tx] = new Block(Block.TYPE_NONE);
}
return row;
}
public function start():void {
resetMap();
if (hasEventListener(GameEvent.GAME_START)){
dispatchEvent(new GameEvent(GameEvent.GAME_START));
}
nextPuzzle = new Puzzle(Block.getRandomType());
updateTimer = new Timer(500);
updateTimer.addEventListener(TimerEvent.TIMER, onUpdate);
updateTimer.start()
}
public function getBlock(tx:int, ty:int):Block {
return map[ty][tx] as Block;
}
private function allowDropPuzzle():Boolean {
var testPuzzle:Puzzle = new Puzzle(nextPuzzle.type);
testPuzzle.position = new Point(Math.floor((cfg.spaceColumn - testPuzzle.blocks[0].length) * 0.5), -1);
return testPuzzle.allowDropDown(this);
}
private function dropPuzzle():void {
activePuzzle = new Puzzle(nextPuzzle.type);
activePuzzle.position = new Point(Math.floor((cfg.spaceColumn - activePuzzle.blocks[0].length) * 0.5), -1);
nextPuzzle = new Puzzle(Block.getRandomType());
}
private function onUpdate(e:TimerEvent):void {
update();
}
private function update():void {
if (activePuzzle == null){
if (allowDropPuzzle()){
dropPuzzle();
} else {
updateTimer.stop();
dispatchEvent(new GameEvent(GameEvent.GAME_OVER));
reset();
}
} else {
if (activePuzzle.allowMoveDown(this)){
activePuzzle.moveDown();
} else {
activePuzzle.appendToMap(this);
activePuzzle = null;
var h:int = map.length;
var w:int = map[0].length;
var ty:int, tx:int;
var cellCount:int;
var removeIdx:Array = new Array();
for (ty = 0; ty < h; ty++){
cellCount = 0;
for (tx = 0; tx < w; tx++){
if (getBlock(tx, ty).type != Block.TYPE_NONE){
cellCount++;
}
}
if (cellCount == w){
removeIdx.push(ty)
}
}
var len:int = removeIdx.length;
var removedRow:Array
if (len > 0){
var i:int = len - 1;
for (i; i > -1; i--){
removedRow = map.splice(removeIdx[i], 1);
}
}
for (i = 0; i < len; i++){
map.unshift(newRow());
}
if (hasEventListener(GameEvent.MAP_UPDATE)){
dispatchEvent(new GameEvent(GameEvent.MAP_UPDATE));
}
}
}
}
}
/*******************************************************/
import flash.display.DisplayObject;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.geom.Rectangle;
class Renderer extends Sprite {
private var cfg:Config;
private var game:Game;
private var bg:Sprite;
private var grid:Sprite;
private var layerBlocks:Sprite;
private var layerPuzzle:Sprite;
private var layerMask:Rectangle;
public function init(config:Config, game:Game):void {
this.game = game;
this.cfg = config;
game.addEventListener(GameEvent.GAME_START, onGameStart);
game.addEventListener(GameEvent.GAME_OVER, onGameOver);
game.addEventListener(GameEvent.GAME_RESET, onGameReset);
game.addEventListener(GameEvent.MAP_UPDATE, onMapUpdate);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
public function build():void {
initBG();
initGrid();
initLayerBlocks();
initLayerPuzzle();
initMask();
}
private function initBG():void {
bg = new Sprite();
var g:Graphics = bg.graphics;
g.beginFill(0xFFFFFF);
g.drawRect(0, 0, cfg.spaceColumn * cfg.blockSize, cfg.spaceRow * cfg.blockSize);
g.endFill();
bg.mouseEnabled = false;
addChild(bg);
}
private function initGrid():void {
grid = new Sprite();
var g:Graphics = grid.graphics;
g.lineStyle(1, 0xCCCCCC, 0.5);
var ty:int, tx:int;
var w:int = cfg.spaceColumn * cfg.blockSize;
var h:int = cfg.spaceRow * cfg.blockSize
for (ty = 1; ty < cfg.spaceRow; ty++){
g.moveTo(0, ty * cfg.blockSize);
g.lineTo(w, ty * cfg.blockSize)
}
for (tx = 1; tx < cfg.spaceColumn; tx++){
g.moveTo(tx * cfg.blockSize, 0);
g.lineTo(tx * cfg.blockSize, h);
}
g.drawRect(0, 0, cfg.spaceColumn * cfg.blockSize, cfg.spaceRow * cfg.blockSize);
grid.mouseEnabled = false;
addChild(grid);
}
private function initMask():void {
layerMask = new Rectangle(0, 0, cfg.spaceColumn * cfg.blockSize + 1, cfg.spaceRow * cfg.blockSize + 1);
this.scrollRect = layerMask;
}
private function initLayerBlocks():void {
layerBlocks = new Sprite();
addChild(layerBlocks);
}
private function drawBlocks():void {
var block:Block;
var clip:DisplayObject;
var ty:uint, tx:uint;
while (layerBlocks.numChildren){
layerBlocks.removeChildAt(0);
}
for (ty = 0; ty < cfg.spaceRow; ty++){
for (tx = 0; tx < cfg.spaceColumn; tx++){
block = game.getBlock(tx, ty) as Block;
if (block != null && block.type != Block.TYPE_NONE){
clip = block.getView(cfg) as DisplayObject;
if (clip != null){
clip.x = tx * cfg.blockSize;
clip.y = ty * cfg.blockSize;
layerBlocks.addChild(clip);
}
}
}
}
}
private function initLayerPuzzle():void {
layerPuzzle = new Sprite();
addChild(layerPuzzle);
}
private function updateLayerPuzzle():void {
drawPuzzle(game.activePuzzle);
}
private function drawPuzzle(puzzle:Puzzle = null):void {
while (layerPuzzle.numChildren){
layerPuzzle.removeChildAt(0);
}
if (puzzle != null){
var puzzleClip:DisplayObject = puzzle.getView(cfg) as DisplayObject;
if (puzzleClip != null){
puzzleClip.x = puzzle.position.x * cfg.blockSize;
puzzleClip.y = puzzle.position.y * cfg.blockSize;
layerPuzzle.addChild(puzzleClip);
}
}
}
private function onEnterFrame(e:Event):void {
update();
}
private function update():void {
updateLayerPuzzle();
}
private function onGameStart(e:Event):void {
//trace("onGameStart");
drawBlocks();
stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
}
private function onKeyDown(e:KeyboardEvent):void {
//trace("onKeyDown:", e.keyCode);
var p:Puzzle = game.activePuzzle;
if (p != null){
//Arrow Key or A,S,D,W
if (e.keyCode == 37 || e.keyCode == 65){
if (p.allowMoveLeft(game)){
p.moveLeft();
}
}
if (e.keyCode == 38 || e.keyCode == 87){
if (p.allowRotateRight(game)){
p.rotateRight();
}
}
if (e.keyCode == 39 || e.keyCode == 68){
if (p.allowMoveRight(game)){
p.moveRight();
}
}
if (e.keyCode == 40 || e.keyCode == 83){
if (p.allowMoveDown(game)){
p.moveDown();
}
}
}
}
private function onMapUpdate(e:Event = null):void {
updateLayerPuzzle();
drawBlocks();
}
private function onGameOver(e:Event = null):void {
trace("onGameOver");
}
private function onGameReset(e:Event = null):void {
trace("onGameReset");
build();
}
}