PageRenderTime 26ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/ntris/Board.as

https://github.com/gkanwar/ntris
ActionScript | 681 lines | 612 code | 61 blank | 8 comment | 149 complexity | 3d51011e8ab7c61db10be8dae82484ae MD5 | raw file
  1. package ntris
  2. {
  3. import flash.display.InteractiveObject;
  4. import flash.display.Sprite;
  5. import flash.geom.Point;
  6. import ntris.Color;
  7. import ntris.Constants;
  8. import ntris.Input;
  9. import ntris.BlockLoader;
  10. public class Board extends Sprite
  11. {
  12. private const PLAYING:int = 0;
  13. private const PAUSED:int = 1;
  14. private const GAMEOVER:int = 2;
  15. private var curBlock:Block;
  16. private var curBlockType:int;
  17. private var heldBlockType:int;
  18. private var boardBlocks:Array = new Array();
  19. private var blocksInRow:Array = new Array();
  20. private var boardState:int;
  21. private var preview:Array = new Array();
  22. private var previewOffset:int;
  23. private var isKeyExecuted:Array = new Array(Input.NUM_KEYS);
  24. private var previewAnim:int;
  25. private var holdUsed:Boolean;
  26. private var boardChanged:Boolean = false;
  27. private var $blockData:Array;
  28. private var score:int;
  29. private var combo:int;
  30. private var frame:int;
  31. private const OK:int = 0;
  32. private const TOPEDGE:int = 1;
  33. private const RIGHTEDGE:int = 2;
  34. private const BOTTOMEDGE:int = 3;
  35. private const LEFTEDGE:int = 4;
  36. private const OVERLAP:int = 5;
  37. private const MAXSHOVEAWAYS:int = 2;
  38. private const PREVIEW:int = 5;
  39. private const PREVIEWANIMFRAMES:int = 3;
  40. private var numBlockTypes:Array;
  41. private var difficultyLevels:int;
  42. public function Board($refBlockData:Array, iDifficultyLevels:int, iNumBlockTypes:Array)
  43. {
  44. frame = 0;
  45. $blockData = $refBlockData;
  46. numBlockTypes = iNumBlockTypes;
  47. difficultyLevels = iDifficultyLevels;
  48. for (var i:int = 0; i < Constants.NUMCOLS; i++)
  49. {
  50. boardBlocks[i] = new Array();
  51. for (var j:int = 0; j < Constants.NUMROWS; j++)
  52. {
  53. blocksInRow[j] = 0;
  54. boardBlocks[i][j] = 0;
  55. }
  56. }
  57. resetBoard();
  58. //DEBUG
  59. curBlockType = 1001
  60. curBlock = new Block();
  61. curBlock.x = $blockData[curBlockType].x;
  62. curBlock.y = $blockData[curBlockType].y;
  63. curBlock.numSquares = $blockData[curBlockType].numSquares;
  64. for (var k:int = 0; k < curBlock.numSquares; k++)
  65. {
  66. curBlock.squares[k].x = $blockData[curBlockType].squares[k].x;
  67. curBlock.squares[k].y = $blockData[curBlockType].squares[k].y;
  68. }
  69. curBlock.color = $blockData[curBlockType].color;
  70. curBlock.rotates = $blockData[curBlockType].rotates;
  71. curBlock.height = $blockData[curBlockType].height;
  72. curBlock.rowsDropped = calculateRowsDropped(curBlock);
  73. }
  74. private function resetBoard():void
  75. {
  76. for (var i:int = 0; i < Input.NUM_KEYS; i++)
  77. {
  78. isKeyExecuted[i] = false;
  79. }
  80. for (var y:int = 0; y < Constants.NUMROWS; y++)
  81. {
  82. for (var x:int = 0; x < Constants.NUMCOLS; x++)
  83. {
  84. boardBlocks[x][y] = 0;
  85. }
  86. blocksInRow[y] = 0;
  87. }
  88. boardChanged = true;
  89. preview = new Array();
  90. previewAnim = 0;
  91. previewOffset = 0;
  92. heldBlockType = -1;
  93. curBlockType = -1;
  94. curBlock = null;
  95. playTetrisGod();
  96. getNextBlock();
  97. score = 0;
  98. combo = 0;
  99. boardState = PLAYING;
  100. }
  101. private function playTetrisGod():void
  102. {
  103. var type:int;
  104. var level:int;
  105. while (preview.length < PREVIEW)
  106. {
  107. level = difficultyLevel(score);
  108. type = Math.floor(Math.random() * numBlockTypes[level]);
  109. preview.push(type);
  110. }
  111. }
  112. private function difficultyLevel(s:int):int
  113. {
  114. if (difficultyLevels == 1)
  115. {
  116. return 0;
  117. }
  118. var x: Number;
  119. var prob: Number;
  120. var ratio: Number;
  121. prob = Math.random();
  122. if (prob < 0)
  123. {
  124. prob += 1;
  125. }
  126. // calculate the ratio r between the probability of different levels
  127. x = 2.0 * (s - Constants.HALFRSCORE) / Constants.HALFRSCORE;
  128. ratio = (Constants.MAXR - Constants.MINR) * (x / Math.sqrt(1 + x * x) + 1) / 2 + Constants.MINR;
  129. // run through difficulty levels and compare p to a sigmoid for each level
  130. for (var i:int = 1; i < difficultyLevels; i++)
  131. {
  132. x = 2.0 * (s - (Constants.SCOREINTERVAL * i)) / Constants.SCOREINTERVAL;
  133. if (prob > Math.pow(ratio, i) * (x / Math.sqrt(1 + x * x) + 1) / 2)
  134. {
  135. return i - 1;
  136. }
  137. }
  138. return difficultyLevels - 1;
  139. }
  140. public function timeStep(inputs:Array):void
  141. {
  142. var firedKeys:Array = inputs[0];
  143. var releasedKeys:Array = inputs[1];
  144. if (firedKeys.indexOf(Input.START) != -1 && !isKeyExecuted[Input.START])
  145. {
  146. if (boardState == GAMEOVER)
  147. {
  148. resetBoard();
  149. }
  150. else if (boardState == PLAYING)
  151. {
  152. boardState = PAUSED;
  153. return;
  154. }
  155. else if (boardState == PAUSED)
  156. {
  157. boardState = PLAYING;
  158. }
  159. isKeyExecuted[Input.START] = true;
  160. }
  161. if (boardState == PLAYING)
  162. {
  163. playTetrisGod();
  164. var trans:Point = new Point(0, 0);
  165. var deltaAngle:int = 0;
  166. for (var i:int = 0; i < firedKeys.length; i++)
  167. {
  168. var key:int = firedKeys[i];
  169. if (key == Input.MOVE_LEFT || key == Input.MOVE_RIGHT)
  170. {
  171. trans.x += (key == Input.MOVE_LEFT) ? -1 : 1;
  172. }
  173. else if ((key == Input.ROTATE_LEFT || key == Input.ROTATE_RIGHT) && !isKeyExecuted[key])
  174. {
  175. deltaAngle += (key == Input.ROTATE_LEFT) ? 3 : 1;
  176. isKeyExecuted[key] = true;
  177. }
  178. else if (key == Input.HARD_DROP && !isKeyExecuted[key])
  179. {
  180. curBlock.y += curBlock.rowsDropped;
  181. placeBlock(curBlock);
  182. getNextBlock();
  183. isKeyExecuted[key] = true;
  184. return;
  185. }
  186. else if (key == Input.SOFT_DROP)
  187. {
  188. trans.y += 1;
  189. }
  190. else if (key == Input.HOLD && !isKeyExecuted[key] && !holdUsed)
  191. {
  192. getNextBlock(true);
  193. isKeyExecuted[key] = true;
  194. return;
  195. }
  196. }
  197. for (var j:int = 0; j < releasedKeys.length; j++)
  198. {
  199. isKeyExecuted[releasedKeys[j]] = false;
  200. }
  201. frame = (frame + 1) % Constants.GRAVITY;
  202. if (frame == 0)
  203. {
  204. trans.y = 1;
  205. }
  206. moveBlock(curBlock, trans, deltaAngle);
  207. }
  208. }
  209. private function moveBlock(block:Block, trans:Point, deltaAngle:int):void
  210. {
  211. var moved:Boolean;
  212. if (trans.x != 0)
  213. {
  214. curBlock.x += trans.x;
  215. if (checkBlock(curBlock) != OK)
  216. {
  217. curBlock.x -= trans.x;
  218. }
  219. else
  220. {
  221. moved = true;
  222. }
  223. }
  224. if (deltaAngle != 0)
  225. {
  226. curBlock.angle += deltaAngle;
  227. trans.x = 0;
  228. while ((checkBlock(curBlock) % OVERLAP == LEFTEDGE) || (checkBlock(curBlock) % OVERLAP == RIGHTEDGE))
  229. {
  230. if (checkBlock(curBlock) % OVERLAP == LEFTEDGE)
  231. {
  232. curBlock.x++;
  233. trans.x++;
  234. }
  235. else
  236. {
  237. curBlock.x--;
  238. trans.x--;
  239. }
  240. }
  241. var check:int = checkBlock(curBlock);
  242. if ((check != OK) && (check % OVERLAP != TOPEDGE))
  243. {
  244. if ((curBlock.shoveaways >= MAXSHOVEAWAYS) || !shoveaway(curBlock))
  245. {
  246. curBlock.angle -= deltaAngle;
  247. curBlock.x -= trans.x;
  248. }
  249. else
  250. {
  251. curBlock.shoveaways++;
  252. moved = true;
  253. }
  254. }
  255. else if (check % OVERLAP == TOPEDGE)
  256. {
  257. var deltaY:int = 1;
  258. curBlock.y++;
  259. while (checkBlock(curBlock) % OVERLAP == TOPEDGE)
  260. {
  261. deltaY++;
  262. curBlock.y++;
  263. }
  264. if (checkBlock(curBlock) == OK)
  265. {
  266. moved = true;
  267. }
  268. else
  269. {
  270. curBlock.angle -= deltaAngle;
  271. curBlock.x -= trans.x;
  272. curBlock.y -= deltaY;
  273. }
  274. }
  275. else
  276. {
  277. moved = true;
  278. }
  279. }
  280. if (moved)
  281. {
  282. curBlock.localStickFrames = Constants.MAXLOCALSTICKFRAMES;
  283. curBlock.rowsDropped = calculateRowsDropped(curBlock);
  284. }
  285. if (curBlock.rowsDropped <= 0)
  286. {
  287. curBlock.globalStickFrames--;
  288. if (!moved)
  289. {
  290. curBlock.localStickFrames--;
  291. }
  292. if ((curBlock.globalStickFrames <= 0) || (curBlock.localStickFrames <= 0))
  293. {
  294. placeBlock(curBlock);
  295. getNextBlock();
  296. }
  297. }
  298. else
  299. {
  300. curBlock.globalStickFrames = Constants.MAXGLOBALSTICKFRAMES;
  301. curBlock.localStickFrames = Constants.MAXLOCALSTICKFRAMES;
  302. curBlock.y += trans.y;
  303. curBlock.rowsDropped -= trans.y;
  304. }
  305. }
  306. private function checkBlock(block:Block):int
  307. {
  308. var point:Point = new Point();
  309. var illegality:int = 0;
  310. var overlapsFound:int = 0;
  311. for (var i:int = 0; i < block.numSquares; i++)
  312. {
  313. if (block.angle % 2 == 0)
  314. {
  315. point.x = block.x + block.squares[i].x * (1 - (block.angle % 4));
  316. point.y = block.y + block.squares[i].y * (1 - (block.angle % 4));
  317. }
  318. else
  319. {
  320. point.x = block.x + block.squares[i].y * ((block.angle % 4) - 2);
  321. point.y = block.y + block.squares[i].x * (2 - (block.angle % 4));
  322. }
  323. if (point.y < 0)
  324. {
  325. if (illegality == 0)
  326. {
  327. illegality = TOPEDGE;
  328. }
  329. }
  330. else if (point.y >= Constants.NUMROWS)
  331. {
  332. if (illegality == 0)
  333. {
  334. illegality = BOTTOMEDGE;
  335. }
  336. }
  337. else if (point.x < 0)
  338. {
  339. if (illegality == 0)
  340. {
  341. illegality = LEFTEDGE;
  342. }
  343. }
  344. else if (point.x >= Constants.NUMCOLS)
  345. {
  346. if (illegality == 0)
  347. {
  348. illegality = RIGHTEDGE;
  349. }
  350. }
  351. else if (boardBlocks[point.x][point.y] > 0)
  352. {
  353. overlapsFound++;
  354. }
  355. }
  356. // the flag returned contains all the information found
  357. // flag%OVERLAP gives any edges the block strayed over
  358. // flag/OVERLAP gives the number of overlaps
  359. // if flag == OK (OK = 0) then the position is legal
  360. return illegality + OVERLAP * overlapsFound;
  361. }
  362. private function shoveaway(block:Block):Boolean
  363. {
  364. for (var i:int = 0; i < 4; i++)
  365. {
  366. if (checkBlock(block) == OK)
  367. {
  368. return true;
  369. }
  370. else
  371. {
  372. block.x -= 1;
  373. if (checkBlock(block) == OK)
  374. {
  375. return true;
  376. }
  377. block.x += 2;
  378. if (checkBlock(block) == OK)
  379. {
  380. return true;
  381. }
  382. block.x -= 1;
  383. if (i == 0)
  384. {
  385. block.y++;
  386. }
  387. else if (i == 1)
  388. {
  389. block.y -= 2;
  390. }
  391. else
  392. {
  393. block.y--;
  394. }
  395. }
  396. }
  397. block.y += 3;
  398. return false;
  399. }
  400. private function calculateRowsDropped(block:Block):int
  401. {
  402. for (var i:int = 0; i < Constants.NUMROWS + 1; i++)
  403. {
  404. if (checkBlock(block) == OK)
  405. {
  406. block.y++;
  407. }
  408. else
  409. {
  410. block.y -= i;
  411. return i - 1;
  412. }
  413. }
  414. return Constants.NUMROWS;
  415. }
  416. private function placeBlock(block:Block):void
  417. {
  418. var point:Point = new Point();
  419. for (var i:int = 0; i < block.numSquares; i++)
  420. {
  421. if (block.angle % 2 == 0)
  422. {
  423. point.x = block.x + block.squares[i].x * (1 - (block.angle % 4));
  424. point.y = block.y + block.squares[i].y * (1 - (block.angle % 4));
  425. }
  426. else
  427. {
  428. point.x = block.x + block.squares[i].y * ((block.angle % 4) - 2);
  429. point.y = block.y + block.squares[i].x * (2 - (block.angle % 4));
  430. }
  431. boardBlocks[point.x][point.y] = block.color;
  432. blocksInRow[point.y]++;
  433. boardChanged = true;
  434. }
  435. removeRows();
  436. }
  437. private function removeRows():int
  438. {
  439. var numRowsRemoved:int = 0;
  440. for (var y:int = Constants.NUMROWS - 1; y >= 0; y--)
  441. {
  442. if (blocksInRow[y] == Constants.NUMCOLS)
  443. {
  444. numRowsRemoved++;
  445. }
  446. else if (numRowsRemoved > 0)
  447. {
  448. for (var x:int = 0; x < Constants.NUMCOLS; x++)
  449. {
  450. boardBlocks[x][y + numRowsRemoved] = boardBlocks[x][y];
  451. boardBlocks[x][y] = 0;
  452. }
  453. blocksInRow[y + numRowsRemoved] = blocksInRow[y];
  454. blocksInRow[y] = 0;
  455. }
  456. }
  457. if (numRowsRemoved > 0)
  458. {
  459. score += ((1 << numRowsRemoved) - 1);
  460. combo++;
  461. }
  462. else
  463. {
  464. combo = 0;
  465. }
  466. return numRowsRemoved;
  467. }
  468. private function getNextBlock(swap:Boolean = false):void
  469. {
  470. var blockType:int;
  471. if ((!swap) || (heldBlockType == -1))
  472. {
  473. blockType = preview.shift();
  474. if (swap)
  475. {
  476. heldBlockType = curBlockType;
  477. }
  478. previewAnim = PREVIEWANIMFRAMES;
  479. previewOffset = ($blockData[blockType].height + 1) * Constants.SQUAREWIDTH / 2;
  480. }
  481. else
  482. {
  483. blockType = heldBlockType;
  484. heldBlockType = curBlockType;
  485. }
  486. curBlockType = blockType;
  487. curBlock = new Block();
  488. curBlock.x = $blockData[blockType].x;
  489. curBlock.y = $blockData[blockType].y;
  490. curBlock.numSquares = $blockData[blockType].numSquares;
  491. for (var i:int = 0; i < curBlock.numSquares; i++)
  492. {
  493. curBlock.squares[i].x = $blockData[blockType].squares[i].x;
  494. curBlock.squares[i].y = $blockData[blockType].squares[i].y;
  495. }
  496. curBlock.color = $blockData[blockType].color;
  497. curBlock.rotates = $blockData[blockType].rotates;
  498. curBlock.height = $blockData[blockType].height;
  499. curBlock.rowsDropped = calculateRowsDropped(curBlock);
  500. if (curBlock.rowsDropped < 0)
  501. {
  502. boardState = GAMEOVER;
  503. }
  504. holdUsed = swap;
  505. }
  506. public function draw():void
  507. {
  508. drawBase();
  509. drawBoardState();
  510. drawBlock(curBlock, true);
  511. drawBlock(curBlock);
  512. //drawGUI();
  513. boardChanged = false;
  514. }
  515. private function drawBase():void
  516. {
  517. graphics.clear();
  518. graphics.lineStyle();
  519. graphics.beginFill(Color.BLACK);
  520. graphics.drawRect(0, 0, Constants.BOARDWIDTH + 2 * Constants.BORDER, Constants.BOARDHEIGHT + 2 * Constants.BORDER);
  521. graphics.lineStyle(2, Color.colorCode(28));
  522. graphics.endFill();
  523. graphics.drawRect(Constants.BORDER / 2, Constants.BORDER / 2, Constants.BOARDWIDTH + Constants.BORDER, Constants.BOARDHEIGHT + Constants.BORDER);
  524. graphics.lineStyle(1, Color.mixedColor(Color.WHITE, Color.BLACK, Color.LAMBDA));
  525. var height:uint = Constants.SQUAREWIDTH * (Constants.NUMROWS - Constants.MAXBLOCKSIZE + 1);
  526. for (var i:int = 0; i < Constants.NUMCOLS; i++)
  527. {
  528. drawLineOffset(Constants.SQUAREWIDTH * i, 0, Constants.SQUAREWIDTH * i, height);
  529. drawLineOffset(Constants.SQUAREWIDTH * (i + 1) - 1, 0, Constants.SQUAREWIDTH * (i + 1) - 1, height);
  530. }
  531. var width:uint = Constants.SQUAREWIDTH * Constants.NUMCOLS;
  532. for (var j:int = 0; j < Constants.NUMROWS - Constants.MAXBLOCKSIZE + 1; j++)
  533. {
  534. drawLineOffset(0, Constants.SQUAREWIDTH * j, width, Constants.SQUAREWIDTH * j);
  535. drawLineOffset(0, Constants.SQUAREWIDTH * (j + 1) - 1, width, Constants.SQUAREWIDTH * (j + 1) - 1);
  536. }
  537. }
  538. private function drawBoardState():void
  539. {
  540. for (var i:int = 0; i < Constants.NUMCOLS; i++)
  541. {
  542. for (var j:int = Constants.MAXBLOCKSIZE - 1; j < Constants.NUMROWS; j++)
  543. {
  544. if (boardBlocks[i][j] > 0)
  545. {
  546. drawSquare(i, j, boardBlocks[i][j]);
  547. }
  548. }
  549. }
  550. }
  551. private function drawBlock(block:Block, isShadow:Boolean = false):void
  552. {
  553. var point:Point = new Point();
  554. for (var i:int = 0; i < block.numSquares; i++)
  555. {
  556. if ((block.angle) % 2 == 0)
  557. {
  558. point.x = block.x + block.squares[i].x * (1 - ((block.angle) % 4));
  559. point.y = block.y + block.squares[i].y * (1 - ((block.angle) % 4));
  560. }
  561. else
  562. {
  563. point.x = block.x + block.squares[i].y * (((block.angle) % 4) - 2);
  564. point.y = block.y + block.squares[i].x * (2 - ((block.angle) % 4));
  565. }
  566. if (isShadow)
  567. {
  568. drawSquare(point.x, point.y + block.rowsDropped, block.color, true);
  569. }
  570. else
  571. {
  572. drawSquare(point.x, point.y, block.color);
  573. }
  574. }
  575. }
  576. private function drawSquare(i:int, j:int, color:uint, isShadow:Boolean = false):void
  577. {
  578. if (j < Constants.MAXBLOCKSIZE - 1)
  579. {
  580. return;
  581. }
  582. var pos:Point = new Point(Constants.SQUAREWIDTH * i, Constants.SQUAREWIDTH * (j - Constants.MAXBLOCKSIZE + 1));
  583. if (isShadow)
  584. {
  585. drawSquare(i, j, Color.BLACK);
  586. graphics.lineStyle(1, color);
  587. graphics.endFill();
  588. for (var k:int = 0; k < 2 * Constants.SQUAREWIDTH - 1; k++)
  589. {
  590. if ((pos.x + pos.y + k) % 4 == 0)
  591. {
  592. if (k < Constants.SQUAREWIDTH)
  593. {
  594. drawLineOffset(pos.x, pos.y + k, pos.x + k, pos.y);
  595. }
  596. else
  597. {
  598. drawLineOffset(pos.x + Constants.SQUAREWIDTH, pos.y - Constants.SQUAREWIDTH + k, pos.x - Constants.SQUAREWIDTH + k, pos.y + Constants.SQUAREWIDTH);
  599. }
  600. }
  601. }
  602. }
  603. else
  604. {
  605. graphics.lineStyle(1, Color.mixedColor(Color.WHITE, color, Color.LAMBDA));
  606. graphics.beginFill(color);
  607. drawRectOffset(pos.x, pos.y, Constants.SQUAREWIDTH - 1, Constants.SQUAREWIDTH - 1);
  608. }
  609. }
  610. private function drawLineOffset(x1:int, y1:int, x2:int, y2:int):void
  611. {
  612. graphics.moveTo(x1 + Constants.BORDER, y1 + Constants.BORDER);
  613. graphics.lineTo(x2 + Constants.BORDER, y2 + Constants.BORDER);
  614. }
  615. private function drawRectOffset(x:int, y:int, w:int, h:int):void
  616. {
  617. graphics.drawRect(x + Constants.BORDER, y + Constants.BORDER, w, h);
  618. }
  619. }
  620. }