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