PageRenderTime 62ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/branches/flex/stc/Platform.as

http://simple-tetris-clone.googlecode.com/
ActionScript | 477 lines | 367 code | 54 blank | 56 comment | 53 complexity | b47575ff338e1ae2f5195acbdfb719c5 MD5 | raw file
  1. /* ========================================================================== */
  2. /* Platform.as */
  3. /* This class contains Flash/Flex especific code. */
  4. /* Copyright (c) 2012 Laurens Rodriguez Oscanoa. */
  5. /* -------------------------------------------------------------------------- */
  6. /* This code is licensed under the MIT license: */
  7. /* http://www.opensource.org/licenses/mit-license.php */
  8. /* -------------------------------------------------------------------------- */
  9. package stc {
  10. import flash.events.Event;
  11. import flash.events.KeyboardEvent;
  12. import flash.events.MouseEvent;
  13. import flash.display.Bitmap;
  14. import flash.display.BitmapData;
  15. import flash.display.MovieClip;
  16. import flash.display.Sprite;
  17. import flash.geom.Rectangle;
  18. import flash.geom.Point;
  19. import flash.media.Sound;
  20. import flash.media.SoundChannel;
  21. import flash.media.SoundTransform;
  22. import flash.text.TextField;
  23. import flash.text.Font;
  24. import flash.text.TextFieldAutoSize;
  25. import flash.text.TextFormat;
  26. import flash.ui.Keyboard;
  27. // Below is the "magic" line that allow us to load our resources in the second frame.
  28. // http://blogs.adobe.com/rgonzalez/2006/06/modular_applications_part_2.html
  29. [Frame(factoryClass="stc.Preloader")]
  30. // Flash platform implementation for tetris game
  31. public class Platform extends PlatformBase {
  32. // -------------------------------------------------------------------------
  33. // UI layout (quantities are expressed in pixels)
  34. // -------------------------------------------------------------------------
  35. // Preview tetromino position
  36. private static const PREVIEW_X:int = 112;
  37. private static const PREVIEW_Y:int = 210;
  38. // Score position and length on screen
  39. private static const SCORE_X:int = 72;
  40. private static const SCORE_Y:int = 52;
  41. private static const SCORE_LENGTH:int = 10;
  42. // Lines position and length on screen
  43. private static const LINES_X:int = 108;
  44. private static const LINES_Y:int = 34;
  45. private static const LINES_LENGTH:int = 5;
  46. // Level position and length on screen
  47. private static const LEVEL_X:int = 108;
  48. private static const LEVEL_Y:int = 16;
  49. private static const LEVEL_LENGTH:int = 5;
  50. // Tetromino subtotals position
  51. private static const TETROMINO_X:int = 425;
  52. private static const TETROMINO_L_Y:int = 53;
  53. private static const TETROMINO_I_Y:int = 77;
  54. private static const TETROMINO_T_Y:int = 101;
  55. private static const TETROMINO_S_Y:int = 125;
  56. private static const TETROMINO_Z_Y:int = 149;
  57. private static const TETROMINO_O_Y:int = 173;
  58. private static const TETROMINO_J_Y:int = 197;
  59. private static const TETROMINO_LENGTH:int = 5;
  60. // Tetromino total position
  61. private static const PIECES_X:int = 418;
  62. private static const PIECES_Y:int = 221;
  63. private static const PIECES_LENGTH:int = 6;
  64. // Size of number
  65. private static const NUMBER_WIDTH:int = 7;
  66. private static const NUMBER_HEIGHT:int = 9;
  67. // Symbol names
  68. private static const BMP_BACK:String = "mcBmpBack";
  69. private static const BMP_TILE_BLOCKS:String = "mcBmpBlocks";
  70. private static const FLA_POPUP_PAUSE:String = "mcPopUpPaused";
  71. private static const FLA_POPUP_OVER:String = "mcPopUpOver";
  72. // Symbol names
  73. private static const MUSIC_VOLUME:Number = 0.2;
  74. private static const LINE_VOLUME:Number = 0.5;
  75. private static const MUSIC_LOOP_START:int = 3693;
  76. // Platform data
  77. private var mPopUp:Sprite;
  78. private var mPopUpLabel:TextField;
  79. private var mPopUpCredits:TextField;
  80. private var mBmpCanvas:BitmapData;
  81. private var mBmpTextCanvas:BitmapData;
  82. private var mBmpBlocks:BitmapData;
  83. private var mBmpNumbers:BitmapData;
  84. private var mMusicSound:Sound;
  85. private var mDropSound:Sound;
  86. private var mRowSound:Sound;
  87. private var mMusicChannel:SoundChannel;
  88. private var mMusicPosition:Number;
  89. private var mIsMuted:Boolean;
  90. private var mRefreshBoard:Boolean;
  91. private var mRefreshFrames:int;
  92. public function Platform() {
  93. if (stage) {
  94. init(null);
  95. }
  96. else {
  97. addEventListener(Event.ADDED_TO_STAGE, init);
  98. }
  99. }
  100. // Initializes platform.
  101. public function init(event:Event):void {
  102. removeEventListener(Event.ADDED_TO_STAGE, init);
  103. stage.quality = "HIGH";
  104. // Platform Setup
  105. mGame = new Game(this);
  106. mGame.startGame();
  107. // Load background and add it to scene
  108. var className:Class = Assets.mcBmpBack;
  109. var bmpData:BitmapData = new className().bitmapData as BitmapData;
  110. addChild(new Bitmap(bmpData));
  111. // Create canvas for drawing tiles
  112. mBmpCanvas = new BitmapData(SCREEN_WIDTH, SCREEN_HEIGHT, true, 0);
  113. addChild(new Bitmap(mBmpCanvas));
  114. // Create canvas for drawing text info
  115. mBmpTextCanvas = new BitmapData(SCREEN_WIDTH, SCREEN_HEIGHT, true, 0);
  116. addChild(new Bitmap(mBmpTextCanvas));
  117. // Load tile images
  118. className = Preloader.mcBmpBlocks;
  119. mBmpBlocks = new className().bitmapData as BitmapData;
  120. // Load number images
  121. className = Assets.mcBmpNumbers;
  122. mBmpNumbers = new className().bitmapData as BitmapData;
  123. // Create popup
  124. mPopUp = new MovieClip();
  125. var popupBack:Sprite = new Sprite;
  126. popupBack.graphics.beginFill(0x000000);
  127. popupBack.graphics.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
  128. popupBack.graphics.endFill();
  129. popupBack.alpha = 0.6;
  130. mPopUp.addChild(popupBack);
  131. var textFormat:TextFormat = new TextFormat();
  132. textFormat.font = "ConsoleEx";
  133. textFormat.size = 28;
  134. textFormat.letterSpacing = 3;
  135. textFormat.color = 0xBBBBBB;
  136. mPopUpLabel = new TextField();
  137. mPopUpLabel.embedFonts = true;
  138. mPopUpLabel.selectable = false;
  139. mPopUpLabel.autoSize = TextFieldAutoSize.CENTER;
  140. mPopUpLabel.defaultTextFormat = textFormat;
  141. mPopUpLabel.x = SCREEN_WIDTH / 2;
  142. mPopUpLabel.y = SCREEN_HEIGHT / 2 - 20;
  143. mPopUp.addChild(mPopUpLabel);
  144. var creditFormat:TextFormat = new TextFormat();
  145. creditFormat.font = "ConsoleEx";
  146. creditFormat.size = 13;
  147. creditFormat.letterSpacing = 1.5;
  148. creditFormat.color = 0xFFFFFF;
  149. mPopUpCredits = new TextField();
  150. mPopUpCredits.embedFonts = true;
  151. mPopUpCredits.selectable = false;
  152. mPopUpCredits.autoSize = TextFieldAutoSize.CENTER;
  153. mPopUpCredits.defaultTextFormat = creditFormat;
  154. mPopUpCredits.text = "Programming: Laurens Rodriguez\n"
  155. +" Music: Jarno Alanko";
  156. mPopUpCredits.x = SCREEN_WIDTH / 2 - 140;
  157. mPopUpCredits.y = SCREEN_HEIGHT / 2 + 65;
  158. mPopUp.addChild(mPopUpCredits);
  159. mPopUp.visible = false;
  160. addChild(mPopUp);
  161. // Registering events
  162. stage.addEventListener(Event.ENTER_FRAME, onEnterFrame, false, 0, true);
  163. stage.addEventListener(KeyboardEvent.KEY_DOWN, readInput, false, 0, true);
  164. stage.addEventListener(MouseEvent.CLICK, onMouseClick, false, 0, true);
  165. // Play music background
  166. className = Assets.musicGame;
  167. mMusicSound = new className() as Sound;
  168. mMusicChannel = mMusicSound.play(0, 0, new SoundTransform(MUSIC_VOLUME));
  169. mMusicChannel.addEventListener(Event.SOUND_COMPLETE, onSoundComplete, false, 0, true);
  170. mMusicPosition = 0;
  171. mIsMuted = false;
  172. // Load sound effects
  173. className = Assets.soundRow;
  174. mRowSound = new className() as Sound;
  175. className = Assets.soundDrop;
  176. mDropSound = new className() as Sound;
  177. mRefreshBoard = false;
  178. mRefreshFrames = 0;
  179. }
  180. // Called if it's necessary to redraw the board.
  181. override public function onTetrominoLand():void {
  182. mRefreshBoard = true;
  183. mDropSound.play(0, 0, new SoundTransform(MUSIC_VOLUME));
  184. }
  185. // Called when a row is filled.
  186. override public function onFilledRows():void {
  187. mRowSound.play(0, 0, new SoundTransform(LINE_VOLUME));
  188. }
  189. // Makes the background music to loop in section
  190. public function onSoundComplete(event:Event):void {
  191. if (mMusicChannel) {
  192. mMusicChannel.removeEventListener(Event.SOUND_COMPLETE, onSoundComplete);
  193. mMusicChannel = mMusicSound.play(MUSIC_LOOP_START, 0, new SoundTransform(MUSIC_VOLUME));
  194. mMusicChannel.addEventListener(Event.SOUND_COMPLETE, onSoundComplete, false, 0, true);
  195. }
  196. }
  197. // Called every frame
  198. public function onEnterFrame(event:Event):void {
  199. mGame.update();
  200. }
  201. // Called on mouse click
  202. public function onMouseClick(event:MouseEvent):void {
  203. if (mGame.isPaused) {
  204. mGame.events |= Game.EVENT_PAUSE;
  205. }
  206. else if (mGame.isOver) {
  207. mGame.events |= Game.EVENT_RESTART;
  208. }
  209. }
  210. // Read input device and notify game
  211. public function readInput(event:KeyboardEvent):void {
  212. // On key pressed
  213. switch (event.keyCode) {
  214. // On quit game
  215. case Keyboard.ESCAPE:
  216. mGame.isOver = true;
  217. onGameOver(mGame.isOver);
  218. break;
  219. case KEY_S:
  220. case Keyboard.DOWN:
  221. mGame.events |= Game.EVENT_MOVE_DOWN;
  222. break;
  223. case KEY_W:
  224. case Keyboard.UP:
  225. mGame.events |= Game.EVENT_ROTATE_CW;
  226. break;
  227. case KEY_A:
  228. case Keyboard.LEFT:
  229. mGame.events |= Game.EVENT_MOVE_LEFT;
  230. break;
  231. case KEY_D:
  232. case Keyboard.RIGHT:
  233. mGame.events |= Game.EVENT_MOVE_RIGHT;
  234. break;
  235. case Keyboard.SPACE:
  236. mGame.events |= Game.EVENT_DROP;
  237. break;
  238. case Keyboard.F5:
  239. if (!mIsMuted && mGame.isOver) {
  240. // Restart music if the game was over.
  241. if (mMusicChannel) {
  242. mMusicChannel.stop();
  243. mMusicChannel.removeEventListener(Event.SOUND_COMPLETE, onSoundComplete);
  244. }
  245. mMusicChannel = mMusicSound.play(0, 0, new SoundTransform(MUSIC_VOLUME));
  246. mMusicChannel.addEventListener(Event.SOUND_COMPLETE, onSoundComplete, false, 0, true);
  247. }
  248. mGame.events |= Game.EVENT_RESTART;
  249. break;
  250. case Keyboard.F1:
  251. mGame.events |= Game.EVENT_PAUSE;
  252. break;
  253. case Keyboard.F2:
  254. mGame.events |= Game.EVENT_SHOW_NEXT;
  255. break;
  256. case Keyboard.F3:
  257. mGame.events |= Game.EVENT_SHOW_SHADOW;
  258. break;
  259. case Keyboard.F4:
  260. if (!mGame.isOver && !mGame.isPaused) {
  261. mIsMuted = !mIsMuted;
  262. if (mMusicChannel) {
  263. mMusicPosition = mMusicChannel.position;
  264. mMusicChannel.stop();
  265. mMusicChannel.removeEventListener(Event.SOUND_COMPLETE, onSoundComplete);
  266. mMusicChannel = null;
  267. }
  268. else {
  269. mMusicChannel = mMusicSound.play(mMusicPosition, 0, new SoundTransform(MUSIC_VOLUME));
  270. mMusicChannel.addEventListener(Event.SOUND_COMPLETE, onSoundComplete, false, 0, true);
  271. }
  272. }
  273. break;
  274. case Keyboard.ENTER:
  275. case Keyboard.NUMPAD_ENTER:
  276. if (!mGame.isOver) {
  277. mGame.masterMode = !mGame.masterMode;
  278. }
  279. break;
  280. }
  281. }
  282. // Called when game is finished/restarted.
  283. override public function onGameOver(isOver:Boolean):void {
  284. if (isOver) {
  285. if (mMusicChannel) {
  286. mMusicChannel.stop();
  287. mMusicChannel.removeEventListener(Event.SOUND_COMPLETE, onSoundComplete);
  288. mMusicChannel = null;
  289. }
  290. mPopUpLabel.text = "GAME OVER";
  291. }
  292. mPopUp.visible = isOver;
  293. mGame.masterMode = false;
  294. }
  295. // Called when game is paused/resumed.
  296. override public function onGamePaused(isPaused:Boolean):void {
  297. if (isPaused) {
  298. if (mMusicChannel) {
  299. mMusicPosition = mMusicChannel.position;
  300. mMusicChannel.stop();
  301. mMusicChannel.removeEventListener(Event.SOUND_COMPLETE, onSoundComplete);
  302. mMusicChannel = null;
  303. }
  304. mPopUpLabel.text = "GAME PAUSED";
  305. }
  306. else if (!mIsMuted) {
  307. mMusicChannel = mMusicSound.play(mMusicPosition, 0, new SoundTransform(MUSIC_VOLUME));
  308. mMusicChannel.addEventListener(Event.SOUND_COMPLETE, onSoundComplete, false, 0, true);
  309. }
  310. mPopUp.visible = isPaused;
  311. }
  312. // Draw a tile from a tetromino
  313. private function drawTile(x:int, y:int, tile:int, shadow:int = 0):void {
  314. var recSource:Rectangle = new Rectangle();
  315. recSource.x = TILE_SIZE * tile;
  316. recSource.y = (TILE_SIZE + 1) * shadow;
  317. recSource.width = TILE_SIZE + 1;
  318. recSource.height = TILE_SIZE + 1;
  319. mBmpCanvas.copyPixels(mBmpBlocks, recSource, new Point(x, y));
  320. }
  321. // Draw a number on the given position
  322. private function drawNumber(x:int, y:int, number:int, length:int, color:int):void {
  323. var recSource:Rectangle = new Rectangle();
  324. recSource.y = NUMBER_HEIGHT * color;
  325. recSource.width = NUMBER_WIDTH;
  326. recSource.height = NUMBER_HEIGHT;
  327. var pos:int = 0;
  328. do {
  329. recSource.x = NUMBER_WIDTH * (number % 10);
  330. mBmpTextCanvas.copyPixels(mBmpNumbers, recSource, new Point(x + NUMBER_WIDTH * (length - pos), y));
  331. number /= 10;
  332. } while (++pos < length);
  333. }
  334. // Render the state of the game using platform functions
  335. override public function renderGame():void {
  336. if (mRefreshFrames > 0) {
  337. if (--mRefreshFrames == 0) {
  338. mGame.stateChanged = true;
  339. }
  340. }
  341. // Don't draw if it's not necessary
  342. if (mGame.stateChanged) {
  343. var i:int, j:int;
  344. // Clear background
  345. mBmpCanvas.fillRect(new Rectangle(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0);
  346. // Draw preview block
  347. if (mGame.showPreview) {
  348. for (i = 0; i < 4; ++i) {
  349. for (j = 0; j < 4; ++j) {
  350. if (mGame.nextBlock.cells[i][j] != Game.EMPTY_CELL) {
  351. drawTile(PREVIEW_X + (TILE_SIZE * i),
  352. PREVIEW_Y + (TILE_SIZE * j), mGame.nextBlock.cells[i][j]);
  353. }
  354. }
  355. }
  356. }
  357. // Draw shadow tetromino
  358. if (mGame.showShadow && mGame.shadowGap > 0) {
  359. for (i = 0; i<4; ++i) {
  360. for (j = 0; j < 4; ++j) {
  361. if (mGame.fallingBlock.cells[i][j] != Game.EMPTY_CELL) {
  362. drawTile(BOARD_X + (TILE_SIZE * (mGame.fallingBlock.x + i)),
  363. BOARD_Y + (TILE_SIZE * (mGame.fallingBlock.y + mGame.shadowGap + j)),
  364. mGame.fallingBlock.cells[i][j], 1);
  365. }
  366. }
  367. }
  368. }
  369. // Draw the cells in the board
  370. if (mGame.masterMode) {
  371. if (mRefreshBoard) {
  372. for (i = 0; i < Game.BOARD_WIDTH; ++i) {
  373. for (j = 0; j < Game.BOARD_HEIGHT; ++j) {
  374. if (mGame.map[i][j] != Game.EMPTY_CELL) {
  375. drawTile(BOARD_X + (TILE_SIZE * i),
  376. BOARD_Y + (TILE_SIZE * j), mGame.map[i][j]);
  377. }
  378. }
  379. }
  380. mRefreshBoard = false;
  381. mRefreshFrames = 10;
  382. }
  383. }
  384. else {
  385. for (i = 0; i < Game.BOARD_WIDTH; ++i) {
  386. for (j = 0; j < Game.BOARD_HEIGHT; ++j) {
  387. if (mGame.map[i][j] != Game.EMPTY_CELL) {
  388. drawTile(BOARD_X + (TILE_SIZE * i),
  389. BOARD_Y + (TILE_SIZE * j), mGame.map[i][j]);
  390. }
  391. }
  392. }
  393. }
  394. // Draw falling tetromino
  395. for (i = 0; i<4; ++i) {
  396. for (j = 0; j < 4; ++j) {
  397. if (mGame.fallingBlock.cells[i][j] != Game.EMPTY_CELL) {
  398. drawTile(BOARD_X + (TILE_SIZE * (mGame.fallingBlock.x + i)),
  399. BOARD_Y + (TILE_SIZE * (mGame.fallingBlock.y + j)),
  400. mGame.fallingBlock.cells[i][j]);
  401. }
  402. }
  403. }
  404. mGame.stateChanged = false;
  405. }
  406. // Update game statistic data
  407. if (mGame.scoreChanged) {
  408. drawNumber(LEVEL_X, LEVEL_Y, mGame.stats.level, LEVEL_LENGTH, Game.COLOR_WHITE);
  409. drawNumber(LINES_X, LINES_Y, mGame.stats.lines, LINES_LENGTH, Game.COLOR_WHITE);
  410. drawNumber(SCORE_X, SCORE_Y, mGame.stats.score, SCORE_LENGTH, Game.COLOR_WHITE);
  411. drawNumber(TETROMINO_X, TETROMINO_L_Y, mGame.stats.pieces[Game.TETROMINO_L], TETROMINO_LENGTH, Game.COLOR_ORANGE);
  412. drawNumber(TETROMINO_X, TETROMINO_I_Y, mGame.stats.pieces[Game.TETROMINO_I], TETROMINO_LENGTH, Game.COLOR_CYAN);
  413. drawNumber(TETROMINO_X, TETROMINO_T_Y, mGame.stats.pieces[Game.TETROMINO_T], TETROMINO_LENGTH, Game.COLOR_PURPLE);
  414. drawNumber(TETROMINO_X, TETROMINO_S_Y, mGame.stats.pieces[Game.TETROMINO_S], TETROMINO_LENGTH, Game.COLOR_GREEN);
  415. drawNumber(TETROMINO_X, TETROMINO_Z_Y, mGame.stats.pieces[Game.TETROMINO_Z], TETROMINO_LENGTH, Game.COLOR_RED);
  416. drawNumber(TETROMINO_X, TETROMINO_O_Y, mGame.stats.pieces[Game.TETROMINO_O], TETROMINO_LENGTH, Game.COLOR_YELLOW);
  417. drawNumber(TETROMINO_X, TETROMINO_J_Y, mGame.stats.pieces[Game.TETROMINO_J], TETROMINO_LENGTH, Game.COLOR_BLUE);
  418. drawNumber(PIECES_X, PIECES_Y, mGame.stats.totalPieces, PIECES_LENGTH, Game.COLOR_WHITE);
  419. mGame.scoreChanged = false;
  420. }
  421. }
  422. }
  423. }