効果
外部フレームワークを書く
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<style>
*{
margin: 0;
padding: 0;
}
#box{
width:320px;
height:450px;
position:absolute;
margin:0 auto;
left:0;
top:20px;
right:0;
bottom:0;
background:gray;
border-radius:10px;
}
#mainDiv{
width:300px;
height:400px;
position:absolute;
margin:0 auto;
left:0;
top:10px;
right:0;
bottom:0;
}
.bottom{
width:600px;
height:30px;
position:absolute;
bottom:1px;
right:1px;
}
.bottom .button1{
position: absolute;
right: 55px;
width: 50px;
font-size: 14px;
}
.bottom .button2{
position: absolute;
right: 5px;
width: 50px;
font-size: 14px;
}
.bottom .span1{
position: absolute;
right: 155px;
color: white;
font-size: 8px;
}
.bottom .span2{
position: absolute;
right: 255px;
color: white;
font-size: 8px;
}
</style>
</head>
<body>
<div id='box'>
<div id='mainDiv'>
</div>
<div class='bottom'>
<span id='score' class='span1'>分数:0</span>
<span id='time' class='span2'>时间:0</span>
<button onclick='start()' class='button1'>开始</button>
<button onclick='stop()' class='button2'>结束</button>
</div>
</div>
<script type="text/javascript" src='tetris.js'></script>
<script type="text/javascript">
</script>
</body>
</html>
効果:
内部キャンバスを追加し、マップを描画します
最初にラインコンストラクターLineを作成します
function Line(ctx,o){
this.x=0,//x坐标
this.y=0,//y坐标
this.startX=0,//开始点x位置
this.startY=0, //开始点y位置
this.endX=0,//结束点x位置
this.endY=0;//结束点y位置
this.thin=false;//设置变细系数
this.ctx=ctx;
this.init(o);
}
Line.prototype.init=function(o){
for(var key in o){
this[key]=o[key];
}
}
Line.prototype.render=function(){
innerRender(this);
function innerRender(obj){
var ctx=obj.ctx;
ctx.save()
ctx.beginPath();
ctx.translate(obj.x,obj.y);
if(obj.thin){
ctx.translate(0.5,0.5);
}
if(obj.lineWidth){//设定线宽
ctx.lineWidth=obj.lineWidth;
}
if(obj.strokeStyle){
ctx.strokeStyle=obj.strokeStyle;
}
//划线
ctx.moveTo(obj.startX, obj.startY);
ctx.lineTo(obj.endX, obj.endY);
ctx.stroke();
ctx.restore();
}
return this;
}
パラメータの設定、描画およびその他の関連メソッドの実行
function Game(el){
this.renderArr=[];//待渲染对象存储数组
this.aliveModel=[];//用来存到底的model组合
this.score=0;//分数
this.time=0;//时间
this.moveCount=1;//计时控制器
}
Game.prototype.init=function(el,score,time){
if(!el) return ;
this.el=el;
this.scoreEL=score;
this.timeEL=time;
var canvas = document.createElement('canvas');//创建画布
canvas.style.cssText="background:darkgrey;border:1px solid grey;";//设置样式
var W = canvas.width = 300; //设置宽度
var H = canvas.height = 400;//设置高度
el.appendChild(canvas);//添加到指定的dom对象中
this.ctx = canvas.getContext('2d');
this.canvas=canvas;
this.w=W;
this.h=H;
this.disX=20;//每个格子的x方向大小
this.disY=20;//每个格子的y方向大小
this.maxX=15;//x方向格子总数
this.maxY=20;//y方向格子总数
this.control();//
this.draw();//绘制
}
//绘制地图
Game.prototype.createMap=function(){
var renderArr = this.renderArr;
var disX = this.disX;
var disY = this.disY;
var maxX=this.maxX;
var maxY=this.maxY;
var rectW = this.w;
var rectH = this.h;
var rect=null;
var color;
for(var i=1;i<maxY;i++){//20行
var line = new Line(this.ctx,{
x:0,
y:0,
startX:0,
startY:i*disY,
endX:this.w,
endY:i*disY,
thin:true,
strokeStyle:'white',
lineWidth:0.2
})
renderArr.push(line);
}
for(var i=1;i<maxX;i++){//15列
var line = new Line(this.ctx,{
x:0,
y:0,
startX:i*disX,
startY:0,
endX:i*disX,
endY:this.h,
thin:true,
strokeStyle:'white',
lineWidth:0.2
})
renderArr.push(line);
}
}
Game.prototype.draw=function(){
this.createMap();//绘制地图
this.render();//渲染
}
現時点でのゲームエリアのグリッドと描画は以下のとおりです。
もう一度モデルを描きましょう
モデル定義:1形状、フィールド形状、2形状、2形状、2形状、エンボス形状など、7種類あります。
変形の定義:1つの形状は2つのタイプで変形でき、Tianの形状は変形できず、他の形状は4つのタイプで変形できます。
モデルの構成:モデルは4つの小さな正方形で構成され、各モデルには4つの小さな正方形のx座標とy座標にアクセスし、それをモデルとして描画するためのブロックの配列があります。
モデルの変形:変形する場合、変形の効果を実現するには、各正方形のX \ Y座標を切り替えるだけで済みます。
モデルのコンストラクターを作成しましょう
//模型构造函数
function Model(o){
this.blocks=[],//存储方块的数组,绘制的时候根据数组来绘制
this.type=1,//模型的形状,默认是一字形(共7种)
this.dir=1,//方向默认为1,总共4种,其中一字形为2种,田字形为1种,其他为4种
this.x=0,//x坐标(只传入第一个x,根据这个x来生成其他的x)
this.y=0,//y坐标(只传入第一个y,根据这个y来生成其他的y)
this.init(o);
}
//初始化
Model.prototype.init=function(o){
for(var key in o){
this[key]=o[key];
}
}
例えば
7つの形を作成するメソッドを追加します(7つの形を配置する方法は4つあるため、区別するための方向と配置方法があります)
//创建七字形1
Model.prototype.createQi1=function(){
var blocks=this.blocks,x=this.x,y=this.y;
switch(this.dir){
case 1://
blocks.push({x:x,y:y});
blocks.push({x:x,y:y-1});
blocks.push({x:x,y:y-2});
blocks.push({x:x+1,y:y-2});
break;
case 2://
blocks.push({x:x+2,y:y});
blocks.push({x:x+1,y:y});
blocks.push({x:x,y:y});
blocks.push({x:x,y:y-1});
break;
case 3://
blocks.push({x:x+1,y:y-2});
blocks.push({x:x+1,y:y-1});
blocks.push({x:x+1,y:y});
blocks.push({x:x,y:y});
break;
case 4://
blocks.push({x:x-2,y:y-1});
blocks.push({x:x-1,y:y-1});
blocks.push({x:x,y:y-1});
blocks.push({x:x,y:y});
break;
}
}
7文字の形状を作成してみてください(入力されるxとyが最初に配置されます)
var model = new Model({//创建1字
x:6,y:6,fillStyle:'#0370BD',fill:true,game:this,type:5,dir:1
});
this.renderArr.push(model);
//当前的模型
this.currentModel=model;
変形方法を記述します(各変形は前の外観に従って反時計回りに90度回転し、各小さな正方形x、yを変更して変更します)
------------------------ >>>
左の図を右の図に変更するにはどうすればよいですか?
-------------------- >>>
X \ Yが変更されていることを除いて、数字をマークすることで簡単に理解できます。1は1、2、または2に対応します。
1マス:x + 2であれば、指定した位置に移動できます。
2つの正方形:xとyの両方を1増やす必要があります
3つの正方形:y +2で問題ありません
4つの正方形:x-1とy +1で十分です
他は同じです、変形の方法について書きましょう
//七1变形
Model.prototype.transformQi1=function(){
var blocks = this.blocks,block2=blocks[1];
switch(this.dir){
case 1://竖着的
tran1();
this.dir=2;
break;
case 2://横着的
tran2();
this.dir=3;
break;
case 3://竖着的
tran3();
this.dir=4;
break;
case 4://横着的
tran4();
this.dir=1;
break;
}
function tran1(){//变成横着的
for(var i=0;i<blocks.length;i++){
block=blocks[i];
if(i==0){
block.x+=2;
}else if(i==1){
block.x+=1;
block.y+=1;
}else if(i==2){
block.y+=2;
}else if(i==3){
block.x-=1;
block.y+=1
}
}
}
function tran2(){//竖着的
for(var i=0;i<blocks.length;i++){
block=blocks[i];
if(i==0){
block.x-=1;
block.y-=2;
}else if(i==1){
block.y-=1;
}else if(i==2){
block.x+=1;
}else if(i==3){
block.y+=1
}
}
}
function tran3(){//变成横着的
for(var i=0;i<blocks.length;i++){
block=blocks[i];
if(i==3){
block.x+=2;
}else if(i==2){
block.x+=1;
block.y-=1;
}else if(i==1){
}else if(i==0){
block.x-=1;
block.y+=1
}
}
}
function tran4(){//竖着的
for(var i=0;i<blocks.length;i++){
block=blocks[i];
if(i==3){
block.x-=1;
block.y-=2;
}else if(i==2){
block.x-=2;
block.y-=1;
}else if(i==1){
block.x-=1;
}else if(i==0){
block.y+=1;
}
}
}
}
変形イベントとしてwキーと上キーを追加し、左移動、右移動、下向き加速のイベントも追加します
//按键的控制
Game.prototype.control=function(){
var that=this;
global.addEventListener('keydown',function(e){
//if(!that.timmer) return ;
switch (e.keyCode){
case 87://w
case 38://上
that.currentModel.transform();//变形
break;
case 83://s
case 40://下
that.currentModel.move('d');//移动
break;
case 65://a
case 37://左
that.currentModel.move('l');//移动
break;
case 68://d
case 39://右
that.currentModel.move('r');//移动
break;
}
//测试用,记得删除
that.render();
});
}
次に、変形してみてください
モバイルメソッドを追加
//移动
Model.prototype.move=function(dir){
var cur = this.game.currentModel,dis=1,blocks = this.blocks;
if(dir=='r'||dir=='ld'){
dis=1
}else if(dir=='l'){
dis=-1;
}else if(dir=='d'){
dis=3;
}
var stopMoveObj = this.stopMove(dir,dis),
val=stopMoveObj.val,resDis=stopMoveObj.resDis;
if(val) {
if(dir=='d'||dir=='ld'){//到底了
[].push.apply(this.game.aliveModel,cur.blocks);//放到已到底的组合中
this.game.renderArr.pop();//当前模型弹出
this.game.clearBlock();//消除
this.game.createModel();//绘制一个新图形
}
return ;//如果返回true 则不能再往这个方向移动
}
if(resDis>0){
dis=resDis;
}
//更新每一个block的位置
for(var i=0;i<blocks.length;i++){
block=blocks[i];
if(dir=='d'||dir=='ld'){
block.y+=dis;
}else{
block.x+=dis;
}
}
}
境界判定と衝突検出を追加します(境界検出は比較的単純であり、早送りするときは衝突検出を慎重に処理する必要があります。コードを参照してください)
//停止移动
Model.prototype.stopMove=function(dir,dis){
var cur = this.game.currentModel,blocks = this.blocks;
var maxX = this.game.maxX,maxY = this.game.maxY,res,temp;
for(var i=0;i<blocks.length;i++){
block=blocks[i];
if(dir=='d'||dir=='ld'){
if(block.y>=maxY-1){//到底了
return {val:true};
}
}else if(dir=='r'){
if(block.x>=maxX-1){//到右边界了
return {val:true};
}
}else if(dir=='l'){
if(block.x<=0){//到左边界了
return {val:true};
}
}
//碰撞检测
temp=this.collide(block,dis,dir);
if(temp.val){
return temp;
}
if(!res || res.resDis==0 || (temp.resDis!=0 && temp.resDis<res.resDis)){
res=temp;
}
}
return res;
}
//检查当前模型是否与其他存底的模型相触碰
Model.prototype.collide=function(block,dis,dir){
var aliveModel = this.game.aliveModel,item;
var res={},val=false,resDis=0,maxY = this.game.maxY;
if(dir=='r'){//向右判断
for(var i=0;i<aliveModel.length;i++){
item = aliveModel[i];
if(!item) continue;
if(item.y==block.y && item.x==block.x+1){
val= true;
break;
}
}
}else if(dir=='l'){//向左判断
for(var i=0;i<aliveModel.length;i++){
item = aliveModel[i];
if(!item) continue;
if(item.y==block.y && item.x==block.x-1){
val= true;
break;
}
}
}else {//向下判断
if(aliveModel.length>0){
for(var i=0;i<aliveModel.length;i++){
item = aliveModel[i];
if(!item) continue;
if(item.x==block.x){//下方有存在的方块
if(item.y==block.y+1){
val= true;
break;
}else if(item.y<=block.y+Math.abs(dis)){
var temp=item.y-block.y-1;
if(resDis==0 || temp<resDis){
resDis = temp;
}
}
}else{//下发不存在方块
if(maxY<=block.y+Math.abs(dis)){
var temp=maxY-block.y-1;
if(resDis==0 || temp<resDis){
resDis = temp;
}
}
}
}
}else{//第一个模型
if(maxY<=block.y+Math.abs(dis)){
var temp=maxY-block.y-1;
if(resDis==0 || temp<resDis){
resDis = temp;
}
}
}
}
res.resDis=resDis;
res.val=val;
return res;
}
終了後の治療
1.現在のゲームオブジェクトのaliveModel配列を指定し、現在のモデルに対応する4つの小さな正方形にアクセスします(renderメソッドが実行されると描画されます)
2.このモデルはrenderArr配列で削除する必要があります
3.ボトムアウトするたびに、排除判断を追加する必要があり、条件が満たされると排除されます。
4.新しいモデルを描画します
底付きモデルブロックを描画します
//绘制存底的图形
Game.prototype.aliveModelRender=function(){
var context=this.ctx;
var disX=this.disX,disY=this.disY;
context.save();
context.beginPath();
_.each(this.aliveModel,function(item){
if(item){
context.rect(item.x*disX+1,item.y*disY+1,disX-1,disY-1);
//context.fillStyle='';
context.fill();
}
});
context.restore();
}
ライン、ポイント、ドロップを排除します
//消除行
Game.prototype.clearBlock=function(){
var maxX=this.maxX,aliveModel=this.aliveModel;
//将y相同的放在一起
var rowArr=[],rowObj={};
_.each(aliveModel,function(item,index){
if(item) {
if(!rowObj[item.y]){
rowObj[item.y]=[];
}
rowObj[item.y].push(index);
}
});
var that=this;
var keys = Object.keys(rowObj),row,num=0;
_.each(keys,function(k){
row = rowObj[k];
if(row.length>=maxX){//消除这行
_.each(row,function(r){
aliveModel.splice(r,1,undefined);//先用undefined代替
})
num++;//行数计数器
that.down(k,1);//清楚当前行
}
})
//完成消除
for(var i=0;i<aliveModel.length;i++){
if(!aliveModel[i]) {
console.log(123)
aliveModel.splice(i,1);
}
}
var score = 0;
switch (num){
case 1:
score=100;//1行100分
break;
case 2:
score=300;//2行300分
break;
case 3:
score=600;//3行600分
break;
case 4:
score=1000;//4行1000分
break;
}
//积分
this.calcuScore(score);
}
//消除后的下降
Game.prototype.down=function(y,num){
var aliveModel=this.aliveModel;
_.each(aliveModel,function(item){
if(item && item.y<y){
item.y+=num;
}
});
}
自動的に下に移動、更新、表示時間、スコア
//显示分数
Game.prototype.calcuScore=function(s){
this.score+=s;
this.scoreEL.innerText='分数:'+this.score;
}
//显示时间
Game.prototype.calcuTime=function(){
if(this.moveCount%4==0){
this.time++;
this.time_flag=false;
this.timeEL.innerText='时间:'+this.time;
}
this.moveCount++;
}
//向下移动
Game.prototype.move=function(dir){
var curModel= this.currentModel;
this.calcuTime();
var endFlag = this.end();
if(endFlag) {
this.stop();
this.hasEnd=true;
return ;
}
this.update();
this.render();
}
//更新
Game.prototype.update=function(){
this.currentModel.move('ld');
}
開始ボタンと終了ボタンにイベントを追加する
var mainDiv = document.getElementById('mainDiv');
var score= document.getElementById('score');
var time= document.getElementById('time');
game.init(mainDiv,score,time);
function start(){
game.start()
}
function stop(){
game.stop()
}
Game.prototype.start=function(){
if(this.timmer) return ;
if(this.hasEnd){//如果是结束则需要重新开始,暂停的话就继续游戏
this.restart();
}
this.hasEnd=false;
this.timmer = setInterval(this.move.bind(this),250);//开始定时任务
}
//重新开始
Game.prototype.restart=function(){
this.renderArr=[];//待渲染对象存储数组
this.aliveModel=[];//用来存到底的model组合
this.score=0;//分数
this.time=0;//时间
this.moveCount=1;//计时控制器
this.clearCanvas();
this.draw();
}
//停止任务
Game.prototype.stop=function(){
if(!this.timmer) return ;
clearInterval(this.timmer);//清除定时任务
this.timmer=null;
}
//结束
Game.prototype.end=function(){
var aliveModel = this.aliveModel;
for(var i=0;i<aliveModel.length;i++){
item = aliveModel[i];
if(item&&item.y==0){
alert('结束了')
return true;
}
}
return false
}