<!DOCTYPE html> <html manifest="tetris.manifest"> <!--在HTML标签里manifest=”cache.manifest”属性是告诉浏览器我们需要缓存那些文件。--> <head> <meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"/> <meta name="apple-mobile-web-app-capable" content="yes" /> <!--apple-mobile-web-app-capable: 这是又一个地方告诉浏览器,它一个离线应用程序--> <meta name="apple-mobile-web-app-status-bar-style" content="black" /> <!--apple-mobile-web-app-status-bar-style:当处于离线时隐藏状态栏和导航栏。--> <link rel="apple-touch-icon" href="iphon_tetris_icon.png"/><!--apple-touch-icon:告诉浏览器程序图标的地址。--> <link rel="apple-touch-startup-image" href="startup.png" /><!--apple-touch-startup-image: 告诉浏览器启动画面的地址。--> <link rel="stylesheet" href="css/tetris.css" type="text/css" media="screen, mobile" title="main" charset="utf-8"><!--还有一点请注意,最好把CSS文件放在上面,JavaScript文件放在下面。--> <title>offline Tetris</title> </head> <body><!--Put your Markup Here--> <script type="text/javascript" src="css/tetris.js"></script> </body> </html>
css:
body {overflow:hidden; background: #d7d7d7;margin:0;padding:0;} #tetris { width: 320px; height: 460px; background:#000; /*border: 1px solid black;*/ } #canvas { position:absolute; background-color: #000; color: #fff; height: 440px; border-right:1px solid #fff; top:20px; left:0; } #canvas h1 {margin: 0; padding: 0;text-align: center; font-size: 30px; padding-top: 200px;} .piece { border: 1px solid white; position: absolute; } .square { position: absolute; width: 19px; height: 19px; border: 1px solid white; } .type0 {background-color: #A000F0;} .type1 {background-color: #00F0F0;} .type2 {background-color: #F0A000;} .type3 {background-color: #0000F0;} .type4 {background-color: #00F000;} .type5 {background-color: #F00000;} .type6 {background-color: #F0F000;} #next_shape {position: relative; top:20px;background-color: #000; border: 1px solid white;width: 100px; height: 90px;border-left:none;} #info {background-color: #000; color: #fff; width: 100px; float:left;} #level {padding-top:10px;} .game_area{ width:220px; height:460px; float:left; } .control { position:absolute; background-color:transparent; border:1px solid #fff; } .move_left, .move_right { height:98px; width:108px; top:260px; } .move_left { left:0px; } .move_right{ left:110px; } .move_rotate{ height:238px; width:218px; left:0px; top:20px; } .move_down, .move_show_controls { width:218px; height:98px; } .move_down { top:360px; left:0px; } .move_pause, .move_show_controls { width:98px; height:98px; } .move_show_controls { top:260px; left:220px; } .move_pause { top:360px; left:220px; } .move_pause div,.move_show_controls div{ color:#fff; text-align:center; font-size:150%; } .for_help { display:none; } .for_help span { position:relative; top:25px; } .show_controls .for_help { color:#fff; display:block; margin:0 auto; text-align:center; vertical-align:middle; /*padding-top:25%;*/ font-size:150%; } #controls { display:inline; height:1px; position:absolute; top:0px; left:0px; } #ownz { position:absolute; top:0; left:0; height:20px; color:#fff; } #ownz a { color:#ddd; }
tetris.js
/// <reference path="tetris.js" /> (function() { var tetris = { board:[], boardDiv:null, canvas:null, pSize:20, canvasHeight:440, canvasWidth:220, boardHeight:0, boardWidth:0, spawnX:4, spawnY:1, shapes:[ [ [-1,1],[0,1],[1,1],[0,0] //TEE ], [ [-1,0],[0,0],[1,0],[2,0] //line ], [ [-1,-1],[-1,0],[0,0],[1,0] //L EL ], [ [1,-1],[-1,0],[0,0],[1,0] //R EL ], [ [0,-1],[1,-1],[-1,0],[0,0] //R ess ], [ [-1,-1],[0,-1],[0,0],[1,0] // L ess ], [ [0,-1],[1,-1],[0,0],[1,0] // square ] ], tempShapes:null, curShape:null, curShapeIndex:null, curX:0, curY:0, curSqs:[], nextShape:null, nextShapeDisplay:null, nextShapeIndex:null, sqs:[], score:0, scoreDisplay:null, level:1, levelDisplay:null, numLevels:10, time:0, maxTime:1000, timeDisplay:null, isActive:0, curComplete:false, timer:null, sTimer:null, speed:700, lines:0, log: function(msg){ if(window["console"] && console["log"]){ console.log(msg); } }, init:function() { this.canvas = document.getElementById("canvas"); this.initBoard(); this.initInfo(); this.initLevelScores(); this.initShapes(); this.bindKeyEvents(); this.bindControlEvents(); this.play(); }, initBoard:function() { this.boardHeight = this.canvasHeight/this.pSize; this.boardWidth = this.canvasWidth/this.pSize; var s = this.boardHeight * this.boardWidth; for (var i=0;i<s;i++) { this.board.push(0); } //this.boardDiv = document.getElementById('board'); // for debugging }, initInfo:function() { this.nextShapeDisplay = document.getElementById("next_shape"); this.levelDisplay = document.getElementById("level").getElementsByTagName("span")[0]; this.timeDisplay = document.getElementById("time").getElementsByTagName("span")[0]; this.scoreDisplay = document.getElementById("score").getElementsByTagName("span")[0]; this.linesDisplay = document.getElementById("lines").getElementsByTagName("span")[0]; this.setInfo('time'); this.setInfo('score'); this.setInfo('level'); this.setInfo('lines'); }, initShapes:function() { this.curSqs = []; this.curComplete = false; this.shiftTempShapes(); this.curShapeIndex = this.tempShapes[0]; this.curShape = this.shapes[this.curShapeIndex]; this.initNextShape(); this.setCurCoords(this.spawnX,this.spawnY); this.drawShape(this.curX,this.curY,this.curShape); }, initNextShape:function() { if (typeof this.tempShapes[1] === 'undefined') {this.initTempShapes();} try { this.nextShapeIndex = this.tempShapes[1]; this.nextShape = this.shapes[this.nextShapeIndex]; this.drawNextShape(); } catch(e) { throw new Error("Could not create next shape. " + e); } }, initTempShapes:function() { this.tempShapes = []; for (var i = 0;i<this.shapes.length;i++) { this.tempShapes.push(i); } var k = this.tempShapes.length; while ( --k ) { //Fisher Yates Shuffle var j = Math.floor( Math.random() * ( k + 1 ) ); var tempk = this.tempShapes[k]; var tempj = this.tempShapes[j]; this.tempShapes[k] = tempj; this.tempShapes[j] = tempk; } }, shiftTempShapes:function() { try { if (typeof this.tempShapes === 'undefined' || this.tempShapes === null) { this.initTempShapes(); } else { this.tempShapes.shift(); } } catch(e) { throw new Error("Could not shift or init tempShapes: " + e); } }, initTimer:function() { var me = this; var tLoop = function() { me.incTime(); me.timer = setTimeout(tLoop,2000); }; this.timer = setTimeout(tLoop,2000); }, initLevelScores:function() { var c = 1; for (var i=1;i<=this.numLevels;i++) { this['level' + i] = [c * 1000,40*i,5*i]; // for next level, row score, p score, TODO: speed c = c + c; } }, setInfo:function(el) { this[el + 'Display'].innerHTML = this[el]; }, drawNextShape:function() { var ns = []; for (var i=0;i<this.nextShape.length;i++) { ns[i] = this.createSquare(this.nextShape[i][0] + 2,this.nextShape[i][1] + 2,this.nextShapeIndex); } this.nextShapeDisplay.innerHTML = ''; for (var k=0;k<ns.length;k++) { this.nextShapeDisplay.appendChild(ns[k]); } }, drawShape:function(x,y,p) { for (var i=0;i<p.length;i++) { var newX = p[i][0] + x; var newY = p[i][1] + y; this.curSqs[i] = this.createSquare(newX,newY,this.curShapeIndex); } for (var k=0;k<this.curSqs.length;k++) { this.canvas.appendChild(this.curSqs[k]); } }, createSquare:function(x,y,type) { var el = document.createElement('div'); el.className = 'square type'+type; el.style.left = x * this.pSize + 'px'; el.style.top = y * this.pSize + 'px'; return el; }, removeCur:function() { var me = this; this.curSqs.eachdo(function() { me.canvas.removeChild(this); }); this.curSqs = []; }, setCurCoords:function(x,y) { this.curX = x; this.curY = y; }, bindKeyEvents:function() { var me = this; var event = "keypress"; if (this.isSafari() || this.isIE()) {event = "keydown";} var cb = function(e) { me.handleKey(e); }; if (window.addEventListener) { document.addEventListener(event, cb, false); } else { document.attachEvent('on' + event,cb); } }, toggleControls: function(){ var classes = this.controls.className.split(" "), cls_length = classes.length, hasClass = false, newClassList = []; this.log(classes); while(cls_length--){ className = classes[cls_length]; if(className == "show_controls"){ hasClass = true; } else { newClassList.push(className); } } if(hasClass == false){ newClassList.push("show_controls"); } this.log(newClassList.join(" ")); this.controls.className = newClassList.join(" "); }, bindControlEvents:function() { var me = this, event = "click", cb = function(e) { me.handleControl(e); }, controls = document.getElementById("controls"); this.controls = controls; if (window.addEventListener) { this.controls.addEventListener(event, cb, false); } else { this.controls.attachEvent('on' + event,cb); } }, handleControl:function(e) { var classes = e.target.className.split(" "), cls_length = classes.length, dir = ''; this.log(classes); while(cls_length--){ class_name = classes[cls_length]; switch (class_name) { case "move_left": this.move('L'); break; case "move_rotate": // rotate this.move('RT'); break; case "move_right": this.move('R'); break; case "move_down": this.move('D'); break; case "move_pause": //esc:pause this.togglePause(); break; case "move_show_controls": //esc:pause this.log("move_show_controls"); this.toggleControls(); break; default: break; } } }, handleKey:function(e) { var c = this.whichKey(e); var dir = ''; switch (c) { case 37: this.move('L'); break; case 38: // rotate this.move('RT'); break; case 39: this.move('R'); break; case 40: this.move('D'); break; case 27: //esc:pause this.togglePause(); break; default: break; } }, whichKey:function(e) { var c; if (window.event) {c = window.event.keyCode;} else if (e) {c = e.keyCode;} return c; }, incTime:function() { this.time++; this.setInfo('time'); }, incScore:function(amount) { this.score = this.score + amount; this.setInfo('score'); }, incLevel:function() { this.level++; this.speed = this.speed - 75; this.setInfo('level'); }, incLines:function(num) { this.lines += num; this.setInfo('lines'); }, calcScore:function(args) { var lines = args.lines || 0; var shape = args.shape || false; var speed = args.speed || 0; var score = 0; if (lines > 0) { score += lines*this["level" + this.level][1]; this.incLines(lines); } if (shape === true) {score += shape*this["level"+this.level][2];} // if (speed > 0) {score += speed*this["level"+this.level[3]];} TODO: implement speed score this.incScore(score); }, checkScore:function() { if (this.score >= this['level' + this.level][0]) { this.incLevel(); } }, gameOver:function() { this.clearTimers(); this.canvas.innerHTML = "<h1>GAME OVER</h1>"; }, play:function() { //gameLoop var me = this; if (this.timer === null) { this.initTimer(); } var gameLoop = function() { me.move('D'); if(me.curComplete) { me.markBoardShape(me.curX,me.curY,me.curShape); me.curSqs.eachdo(function() { me.sqs.push(this); }); me.calcScore({shape:true}); me.checkRows(); me.checkScore(); me.initShapes(); me.play(); } else { me.pTimer = setTimeout(gameLoop,me.speed); } }; this.pTimer = setTimeout(gameLoop,me.speed); this.isActive = 1; }, togglePause:function() { if (this.isActive === 1) { this.clearTimers(); this.isActive = 0; } else {this.play();} }, clearTimers:function() { clearTimeout(this.timer); clearTimeout(this.pTimer); this.timer = null; this.pTimer = null; }, move:function(dir) { var s = ''; var me = this; var tempX = this.curX; var tempY = this.curY; switch(dir) { case 'L': s = 'left'; tempX -= 1; break; case 'R': s = 'left'; tempX += 1; break; case 'D': s = 'top'; tempY += 1; break; case 'RT': this.rotate(); return true; break; default: throw new Error('wtf'); break; } if (this.checkMove(tempX,tempY,this.curShape)) { this.curSqs.eachdo(function(i) { var l = parseInt(this.style[s],10); dir === 'L' ? l-=me.pSize:l+=me.pSize; this.style[s] = l + 'px'; return true; }); this.curX = tempX; this.curY = tempY; } else if (dir === 'D') { //if move is invalid and down, piece must be complete if (this.curY === 1 || this.time === this.maxTime) {this.gameOver(); return false;} this.curComplete = true; } return true; }, rotate:function() { if (this.curShapeIndex !== 6) { // if not the square var temp = []; this.curShape.eachdo(function() { temp.push([this[1] * -1,this[0]]); // (-y,x) }); if (this.checkMove(this.curX,this.curY,temp)) { this.curShape = temp; this.removeCur(); this.drawShape(this.curX,this.curY,this.curShape); } else { throw new Error("Could not rotate!");} } }, checkMove:function(x,y,p) { if (this.isOB(x,y,p) || this.isCollision(x,y,p)) {return false;} return true; }, isCollision:function(x,y,p) { var me = this; var bool = false; p.eachdo(function() { var newX = this[0] + x; var newY = this[1] + y; if (me.boardPos(newX,newY) === 1) {bool = true;} }); return bool; }, isOB:function(x,y,p) { var w = this.boardWidth - 1; var h = this.boardHeight - 1; var bool = false; p.eachdo(function() { var newX = this[0] + x; var newY = this[1] + y; if(newX < 0 || newX > w || newY < 0 || newY > h) {bool = true;} }); return bool; }, getRowState:function(y) { //Empty, Full, or Used var c = 0; for (var x=0;x<this.boardWidth;x++) { if (this.boardPos(x,y) === 1) {c = c + 1;} } if (c === 0) {return 'E';} if (c === this.boardWidth) {return 'F';} return 'U'; }, checkRows:function() { //does check for full lines, removes them, and shifts everything else down /*var me = this; var memo = 0; var checks = (function() { me.curShape.eachdo(function() { if ((this[1] + me.curY) > memo) { return this[1]; } }); })(); console.log(checks);*/ var me = this; var start = this.boardHeight; this.curShape.eachdo(function() { var n = this[1] + me.curY; //this.log(n); if (n < start) {start = n;} }); this.log(start); var c = 0; var stopCheck = false; for (var y=this.boardHeight - 1;y>=0;y--) { switch(this.getRowState(y)) { case 'F': this.removeRow(y); c++; break; case 'E': if (c === 0) { stopCheck = true; } break; case 'U': if (c > 0) { this.shiftRow(y,c); } break; default: break; } if (stopCheck === true) { break; } } if (c > 0) { this.calcScore({lines:c}); } }, shiftRow:function(y,amount) { var me = this; for (var x=0;x<this.boardWidth;x++) { this.sqs.eachdo(function() { if (me.isAt(x,y,this)) { me.setBlock(x,y+amount,this); } }); } me.emptyBoardRow(y); }, emptyBoardRow:function(y) { // empties a row in the board array for (var x=0;x<this.boardWidth;x++) { this.markBoardAt(x,y,0); } }, removeRow:function(y) { for (var x=0;x<this.boardWidth;x++) { this.removeBlock(x,y); } }, removeBlock:function(x,y) { var me = this; this.markBoardAt(x,y,0); this.sqs.eachdo(function(i) { if (me.getPos(this)[0] === x && me.getPos(this)[1] === y) { me.canvas.removeChild(this); me.sqs.splice(i,1); } }); }, setBlock:function(x,y,block) { this.markBoardAt(x,y,1); var newX = x * this.pSize; var newY = y * this.pSize; block.style.left = newX + 'px'; block.style.top = newY + 'px'; }, isAt:function(x,y,block) { // is given block at x,y? if(this.getPos(block)[0] === x && this.getPos(block)[1] === y) {return true;} return false; }, getPos:function(block) { // returns [x,y] block position var p = []; p.push(parseInt(block.style.left,10)/this.pSize); p.push(parseInt(block.style.top,10)/this.pSize); return p; }, getBoardIdx:function(x,y) { // returns board array index for x,y coords return x + (y*this.boardWidth); }, boardPos:function(x,y) { // returns value at this board position return this.board[x+(y*this.boardWidth)]; }, markBoardAt:function(x,y,val) { this.board[this.getBoardIdx(x,y)] = val; }, markBoardShape:function(x,y,p) { var me = this; p.eachdo(function(i) { var newX = p[i][0] + x; var newY = p[i][1] + y; me.markBoardAt(newX,newY,1); }); }, isIE:function() { return this.bTest(/IE/); }, isFirefox:function() { return this.bTest(/Firefox/); }, isSafari:function() { return this.bTest(/Safari/); }, bTest:function(rgx) { return rgx.test(navigator.userAgent); } /*debug:function() { var me = this; var str = ''; for (var i=0;i<me.board.length;i++) { if(i%me.boardWidth === 0) {str += "<br />"} if(me.board[i] === 1) {str += ' X ';} else {str += " * ";} } var par = document.createElement('p'); par.innerHTML = str; me.boardDiv.innerHTML = ''; me.boardDiv.appendChild(par); },*/ }; tetris.init(); })(); if (!Array.prototype.eachdo) { Array.prototype.eachdo = function(fn) { for (var i = 0;i<this.length;i++) { fn.call(this[i],i); } }; } if (!Array.prototype.remDup) { Array.prototype.remDup = function() { var temp = []; for(var i=0; i<this.length; i++) { var bool = true; for(var j=i+1; j<this.length; j++) { if(this[i] === this[j]) {bool = false;} } if(bool === true) {temp.push(this[i]);} } return temp; } }