PageRenderTime 59ms CodeModel.GetById 35ms app.highlight 20ms 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
  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}