久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

      HTML5邊玩邊學(xué)(九)-俄羅斯方塊之?dāng)?shù)據(jù)模型篇

      一、從數(shù)據(jù)出發(fā)還是從界面出發(fā)

      要寫一個俄羅斯方塊小游戲,我們先來一塊考慮一下下面幾個問題:

      1、用什么表示方塊

      2、怎么設(shè)置或者改變方塊的顏色

      3、怎么移動方塊

      4、怎么消除方塊

      請考慮一分鐘后再繼續(xù)向下看。。。。。。

      如果你對上面幾個問題思考,每一個答案都和界面、控件、平臺有關(guān)的話,就是說假如你是用 .Net 的,你的每一個答案都是圍繞著如何利用控件、如何使用窗體、在控件的哪個事件里面改變哪個屬性等等,那么說明你被微軟的 RAD 開發(fā)環(huán)境毒害的不淺,我建議你立刻扔掉 Visual Studio,改用其他輕量級的編程語言和開發(fā)平臺,這樣你可以更多的關(guān)注問題的本身,而不是控件。

      記?。撼绦?= 數(shù)據(jù)結(jié)構(gòu) + 算法

      界面只是數(shù)據(jù)的表象,而數(shù)據(jù)才是問題的本質(zhì)。

      下面,我們將一步一步建立一個俄羅斯方塊小游戲的數(shù)據(jù)模型,當(dāng)整個模型建立完畢后,我們會發(fā)現(xiàn),雖然沒有界面,仍然不妨礙這是一個功能完整的俄羅斯方塊游戲,因?yàn)榘l(fā)生的每一件事情都很清楚,我們只是沒把它畫而已。當(dāng)然,后面我們會給出一個操作簡易的界面,等到下一篇,會專門探討界面的問題。

      二、“形狀”的數(shù)據(jù)模型

      俄羅斯方塊是一個經(jīng)久不衰的小游戲,最常見的版本中一般有七個形狀,分別是:

      直線型、S型、Z型、L型、反L型、T型、方形等,如下圖:

      HTML5邊玩邊學(xué)(九)-俄羅斯方塊之?dāng)?shù)據(jù)模型篇

      那么我們在程序中如何表示這七個形狀呢?我們發(fā)現(xiàn)每一形狀都是四個小方塊組成的,我們完全可以用四個點(diǎn)表示。

      但是問題又來了,四個點(diǎn)的坐標(biāo)分別是什么呢?我查到的方法是:每個形狀都有一個自己的坐標(biāo)系,比如S型,可以入下圖表示:

      HTML5邊玩邊學(xué)(九)-俄羅斯方塊之?dāng)?shù)據(jù)模型篇

      這樣,S型的數(shù)據(jù)模型可以表示為四個點(diǎn)組成的數(shù)組:[ [ 0, -1 ], [ 0, 0 ], [ -1, 0 ], [ -1, 1 ] ] 。

      我們可以用同樣的方法建立其他形狀的數(shù)組模型,然后再將這七個形狀的數(shù)組模型合起來組成一個大的數(shù)組。

      另外,每個形狀可以是單色,也可以有自己的顏色。增加顏色會增加編程的復(fù)雜度,但是也增加不了多少,所以我們的模型中也會考慮顏色。

      最后,我們最好給每個形狀一個編號,這樣方便在形狀數(shù)組和顏色數(shù)組中應(yīng)用他們。

      完成上面的分析后,我們就可以給出形狀數(shù)據(jù)模型的代碼了:

      形狀模型的代碼         Code highlighting produced by Actipro CodeHighlighter (freeware)  http://www.CodeHighlighter.com/    -->//各種形狀的編號,0代表沒有形狀  NoShape=0;  ZShape=1;  SShape=2;  LineShape=3;  TShape=4;  SquareShape=5;  LShape=6;  MirroredLShape=7    //各種形狀的顏色  Colors=["black","fuchsia","#cff","red","orange","aqua","green","yellow"];    //各種形狀的數(shù)據(jù)描述  Shapes=[      [ [ 0, 0 ],   [ 0, 0 ],   [ 0, 0 ],   [ 0, 0 ] ],      [ [ 0, -1 ],  [ 0, 0 ],   [ -1, 0 ],  [ -1, 1 ] ],      [ [ 0, -1 ],  [ 0, 0 ],   [ 1, 0 ],   [ 1, 1 ] ],      [ [ 0, -1 ],  [ 0, 0 ],   [ 0, 1 ],   [ 0, 2 ] ],      [ [ -1, 0 ],  [ 0, 0 ],   [ 1, 0 ],   [ 0, 1 ] ],      [ [ 0, 0 ],   [ 1, 0 ],   [ 0, 1 ],   [ 1, 1 ] ],      [ [ -1, -1 ], [ 0, -1 ],  [ 0, 0 ],   [ 0, 1 ] ],      [ [ 1, -1 ],  [ 0, -1 ],  [ 0, 0 ],   [ 0, 1 ] ]  ];

      三、定位和旋轉(zhuǎn)形狀

      1、定位

      我們上面說到每個形狀都是在自己的坐標(biāo)系里面描述的,另外還有一個全局坐標(biāo)系,用來給形狀定位,這樣我們就需要一個方法將形狀的四個點(diǎn)從自身坐標(biāo)系轉(zhuǎn)換到全局坐標(biāo)系,從而給形狀定位。

      假如S型在自身坐標(biāo)系中四個點(diǎn)的坐標(biāo)為:[ [ 0, -1 ], [ 0, 0 ], [ -1, 0 ], [ -1, 1 ] ]

      它當(dāng)前在全局坐標(biāo)系位置為:[12,8]

      則,四個點(diǎn)轉(zhuǎn)換為全局坐標(biāo)系的坐標(biāo)為:[ [ 0+12, -1+8 ], [ 0+12, 0+8 ], [ -1+12, 0+8 ], [ -1+12, 1+8 ] ]

      這樣,我們就完成了 S型 的全局坐標(biāo)轉(zhuǎn)換。

      這里需要注意一個問題,形狀自身坐標(biāo)系是用 (x,y) 描述的,而全局坐標(biāo)系為了邏輯上更直觀,是用 (row,col) 描述的,所以我們在實(shí)際編程中并不是向上面那樣轉(zhuǎn)換的,而是:

      [ [ -1+12, 0+8 ], [ 0+12, 0+8 ], [ 0+12, -1+8 ], [ 1+12, -1+8 ] ]

      即:先將 x 變?yōu)?col ,y 變?yōu)?row ,再轉(zhuǎn)換為全局坐標(biāo)系。

      2、旋轉(zhuǎn)

      旋轉(zhuǎn)是在形狀的自身坐標(biāo)系中,并圍繞形狀的原點(diǎn)完成的,公式很簡單,每個點(diǎn)旋轉(zhuǎn)后的坐標(biāo)與旋轉(zhuǎn)前坐標(biāo)的關(guān)系如下(向右旋轉(zhuǎn)):

      x' = y

      y' = -x

      注意:方塊形狀不發(fā)生旋轉(zhuǎn)。

      有了上面的分析,我們就可以給出兩個全局方法,他們用來對形狀進(jìn)行全局定位和旋轉(zhuǎn):

      全局定位和旋轉(zhuǎn)的代碼         Code highlighting produced by Actipro CodeHighlighter (freeware)  http://www.CodeHighlighter.com/    -->//將形狀自身的坐標(biāo)系轉(zhuǎn)換為  Map 的坐標(biāo)系,row col 為當(dāng)前形狀原點(diǎn)在 Map 中的位置  function translate(data,row,col){      var copy=[];      for(var i=0;i<4;i++){          var temp={};          temp.row=data[i][1]+row;          temp.col=data[i][0]+col;          copy.push(temp);      }      return copy;  }    //向右旋轉(zhuǎn)一個形狀:x'=y, y'=-x  function rotate(data){      var copy=[[],[],[],[]];      for(var i=0;i<4;i++){          copy[i][0]=data[i][1];          copy[i][1]=-data[i][0];      }      return copy;  }

      四、移動空間

      前面我們說過,形狀是由四個點(diǎn)組成的,而形狀的移動空間也是由 m * n 個點(diǎn)組成的一個二維數(shù)組。

      這里為了更直觀的描述,我將 n 個點(diǎn)組成一條線 Line,再將 m 條 Line 組成形狀的移動空間,我把它叫做 Map 。

      我們有了這 m * n 個點(diǎn)有什么用呢?用處很簡單,就是保存形狀的編號,如果一個點(diǎn)沒有被形狀占用,則編號為 NoShape。這就是前面給出形狀編號的用處,同時也是為什么要有一個 NoShape 編號的原因。

      Map 應(yīng)該具有什么功能呢?下面我列舉了一些:

      1、構(gòu)造函數(shù):這不用說了,n 個點(diǎn)組成一行 Line, m 行 Line 組成Map,每個點(diǎn)初始化成 NoShape

      2、newLine:生成新的一行。為什么需要這個方法呢,因?yàn)槌藰?gòu)造函數(shù)中,游戲運(yùn)行過程中我們也需要用到它,當(dāng)一行或者幾行被消除以后,我們需要在頂部假如一行或者幾行新的Line

      3、isFullLine(row):這個方法用來判斷第 row 行是否滿了,每次一個形狀落地后,就需要對每一行進(jìn)行這個判斷,滿了當(dāng)然是消除了。

      4、isCollide(data): data 是一個定位后的形狀數(shù)據(jù),這樣我們就可以檢查這些數(shù)據(jù)是否超出移動空間的上下左右邊界,另外還檢查數(shù)據(jù)的四個點(diǎn)是否已經(jīng)被占用,這就是碰撞檢測。

      5、appendShape(shape_id,data):當(dāng)一個形狀落地以后,我們就應(yīng)該將運(yùn)行空間中某些點(diǎn)的值改變?yōu)檫@個形狀的編號,我把這稱為占用。

      6、消除操作:這個功能沒有單獨(dú)列為一個方法,我把它放在 appendShape 方法中了。消除操作也很簡單,發(fā)現(xiàn)某一行 isFullLine 了以后,在 lines 數(shù)組中移除這一行,并在 lines 數(shù)組的頂部加入一個空行即可。

      有了上面的分析,我們就可以給出移動空間的代碼了:

      移動空間的代碼         Code highlighting produced by Actipro CodeHighlighter (freeware)  http://www.CodeHighlighter.com/    -->/*   * 說明:由 m 行 Line 組成的格子陣   */  function  Map(w,h){      //游戲區(qū)域的長度和寬度      this.width=w;      this.height=h;      //生成 height 個 line 對象,每個 line 寬度為 width      this.lines=[];      for(var row=0;row<h;row++)          this.lines[row]=this.newLine();  }    //說明:間由 n 個格子組成的一行  Map.prototype.newLine=function(){      var shapes=[];      for(var col=0;col<this.width;col++)          shapes[col]=NoShape;      return shapes;  }    //判斷一行是否全部被占用  //如果有一個格子為 NoShape 則返回 false  Map.prototype.isFullLine=function(row){      var line=this.lines[row];      for(var col=0;col<this.width;col++)          if(line[col]==NoShape)              return false      return true;  }  /*   * 預(yù)先移動或者旋轉(zhuǎn)形狀,然后分析形狀中的四個點(diǎn)是否有碰撞情況:   *      1:col<0 || col>this.width 超出左右邊界   *      2:row==this.height ,說明形狀已經(jīng)到最底部   *      3:任意一點(diǎn)的 shape_id 不為 NoShape ,則發(fā)生碰撞   *  如果發(fā)生碰撞則放棄移動或者旋轉(zhuǎn)   */  Map.prototype.isCollide=function(data){      for(var i=0;i<4;i++){          var row=data[i].row;          var col=data[i].col;          if(col<0 || col==this.width) return true;          if(row==this.height) return true;          if(row<0) continue;          else              if(this.lines[row][col]!=NoShape)                  return true;      }      return false;  }    //形狀在向下移動過程中發(fā)生碰撞,則將形狀加入到 Map 中  Map.prototype.appendShape=function(shape_id,data){      //對于形狀的四個點(diǎn):      for(var i=0;i<4;i++){          var row=data[i].row;          var col=data[i].col;          //找到所在的格子,將格子的顏色改為形狀的顏色          this.lines[row][col]=shape_id;      }      //========================================      //形狀被加入到 Map 中后,要進(jìn)行逐行檢測,發(fā)現(xiàn)滿行則消除      for(var row=0;row<this.height;row++){          if(this.isFullLine(row)){              //將滿的那一行替換成新的空,這一步主要是為了顯示效果,可以不要!              //this.lines[row]=null;              //重繪 Map 消除效果              //onClearLine(row);              //將滿行刪除              this.lines.splice(row,1);              //第一行添加新的一行              this.lines.unshift(this.newLine());              //重繪 Map 整行下落效果              onDraw(this.lines);          }      }  }

      五、游戲模型

      我們有了游戲的數(shù)據(jù)模型,我們就可以讀寫他們了。所謂讀好理解,所謂寫就是改變他們,改變的方法當(dāng)然是用戶的操作了。

      下面給出 GameModel 類,他維護(hù)三個主要的數(shù)據(jù):

      1、一個形狀的編號,就是用戶可以操作移動的那個形狀

      2、形狀的全局位置,用 row col 表示

      3、一個 Map,用它完成碰撞檢測,添加等操作

      另外,還抽象出幾個用戶的操作動作:

      1、left:左移。將形狀的全局坐標(biāo) col 減少 1 。請思考一下,這樣就可以了嗎?當(dāng)然不行,我們還需要進(jìn)行碰撞檢測,如果已經(jīng)在最左邊,則放棄處理。

      2、right:右移。同上。

      3、rotate:旋轉(zhuǎn)。同上。

      4、down:下落。同上。下落過程中的碰撞檢測有所不同,一旦發(fā)生碰撞,我們不能再放棄處理了,而是要將當(dāng)前形狀加入到空間中。

      5、GameOver:下落過程中還需要進(jìn)行一個檢測就是游戲是否結(jié)束。如果當(dāng)前形狀在出生地點(diǎn)剛一下落就發(fā)生碰撞,說明已經(jīng)到頂部了,則游戲結(jié)束。

      有了上面的分析,我們就可以給出 GameModel 的代碼:

      GameModel 代碼         Code highlighting produced by Actipro CodeHighlighter (freeware)  http://www.CodeHighlighter.com/    -->/*   * 說明:GameModel 類   */  function GameModel(w,h){      this.map=new Map(w,h);      this.born();  }    //出生一個新的形狀  GameModel.prototype.born=function(){      //隨機(jī)選擇一個形狀      this.shape_id=Math.floor(Math.random()*7)+1;      this.data=Shapes[this.shape_id];      //重置形狀的位置為出生地點(diǎn)      this.row=1;      this.col=Math.floor(this.map.width/2);      //通知繪制移動效果,傳回數(shù)據(jù)為形狀的四個點(diǎn)在 Map 中的位置      onMove(this.shape_id,this.map,translate(this.data,this.row,this.col));  }    //向左移動  GameModel.prototype.left=function(){      this.col--;      var temp=translate(this.data,this.row,this.col);      if(this.map.isCollide(temp))      //發(fā)生碰撞則放棄移動          this.col++;      else      //通知繪制移動效果,傳回數(shù)據(jù)為形狀的四個點(diǎn)在 Map 中的位置          onMove(this.shape_id,this.map,temp);  }    //向右移動  GameModel.prototype.right=function(){      this.col++;      var temp=translate(this.data,this.row,this.col);      if(this.map.isCollide(temp))          this.col--;      else          onMove(this.shape_id,this.map,temp);  }    //旋轉(zhuǎn)  GameModel.prototype.rotate=function(){      //正方形不旋轉(zhuǎn)      if(this.shape_id==SquareShape) return;      //獲得旋轉(zhuǎn)后的數(shù)據(jù)      var copy=rotate(this.data);      //轉(zhuǎn)換坐標(biāo)系      var temp=translate(copy,this.row,this.col);      //發(fā)生碰撞則放棄旋轉(zhuǎn)      if(this.map.isCollide(temp))          return;      //將旋轉(zhuǎn)后的數(shù)據(jù)設(shè)為當(dāng)前數(shù)據(jù)      this.data=copy;      //通知繪制移動效果,傳回數(shù)據(jù)為形狀的四個點(diǎn)在 Map 中的位置      onMove(this.shape_id,this.map,translate(this.data,this.row,this.col));  }    //下落  GameModel.prototype.down=function(){      var old=translate(this.data,this.row,this.col);      this.row++;      var temp=translate(this.data,this.row,this.col);      if(this.map.isCollide(temp)){          //發(fā)生碰撞則放棄下落          this.row--;          //如果在 1 也無法下落,說明游戲結(jié)束          if(this.row==1) {              //通知游戲結(jié)束              //onGameOver();              alert("Game Over")              return;          }          //無法下落則將當(dāng)前形狀加入到 Map 中          this.map.appendShape(this.shape_id,old);          //出生一個新的形狀          this.born();      }      else      //通知繪制移動效果,傳回數(shù)據(jù)為形狀的四個點(diǎn)在 Map 中的位置          onMove(this.shape_id,this.map,temp);  }

      六、一個簡單的操作界面

      雖然到現(xiàn)在為止,我們沒有給出一行和界面有關(guān)的代碼,但是整個游戲在邏輯上已經(jīng)完全可以運(yùn)行起來了,只是我們沒有把他畫出來而已,要想把他畫出來也很簡單。

      注意上面給出的代碼中很多地方調(diào)用了兩個全局函數(shù):onDraw 和 onMove ,這兩個函數(shù)就是用來進(jìn)行繪制的。

      繪制的代碼其實(shí)只占很少的一部分,其中一些繪圖函數(shù)我為了方便對 HTML5 的 2D 函數(shù)進(jìn)行了簡單的封裝,您完全可以用原生的 HTML5 函數(shù),或者用您自己平臺的繪圖函數(shù),因?yàn)樗麄儽旧聿皇翘珡?fù)雜。

      另外有一個全局變量 Spacing ,他表示一個格子的寬度。

      下面給出操作界面的代碼:

      界面操作代碼         Code highlighting produced by Actipro CodeHighlighter (freeware)  http://www.CodeHighlighter.com/    -->//每一格的間距,也即一個小方塊的尺寸  Spacing=20;    //在內(nèi)存中繪制一個小方塊  function drawRect(color){      var temp=new Surface(Spacing,Spacing,"rgba(255,255,255,0.2)");//背景色      temp.fillRect(1, 1, Spacing-2, Spacing-2, color);//前景色      return temp;  }    var display= Display.attach(document.getElementById("html5_09_1"));  var model = new GameModel(display.width/Spacing,display.height/Spacing);      function onDraw(map){      //清屏      display.clear();      var lines=map.lines;      //依次繪制每一個非空的格子      for(var row=0;row<map.height;row++)          for(var col=0;col<map.width;col++){              var shape_id=lines[row][col];              if(shape_id!=NoShape){                  var rect = drawRect(Colors[shape_id]);                  var y=row * Spacing;                  var x=col * Spacing;                  display.draw(rect, x, y);              }      }  }    function onMove(shape_id,map,data){      onDraw(map);      //繪制當(dāng)前的形狀      for(var i=0;i<4;i++){          var y=data[i].row * Spacing;          var x=data[i].col * Spacing;          var rect = drawRect(Colors[shape_id]);          display.draw(rect, x, y);      }  }    function down(){      model.down();  }    function left(){      model.left();  }    function right(){      model.right();  }    function rotate_click(){      model.rotate();  }

      七、如何改進(jìn)

      到現(xiàn)在為止,程序已經(jīng)基本能運(yùn)行起來了,但是還沒有加入鍵盤操作,另外還有一個很大的問題就是:程序有時候會“算死”。為什么會出現(xiàn)這個現(xiàn)象呢?

      做個實(shí)驗(yàn),不管你用什么平臺,你用繪圖函數(shù),先清屏,然后隨機(jī)繪制一條直線,連續(xù)循環(huán)1000次。你會發(fā)現(xiàn),前面999次,并看不到清屏和繪制效果,而且程序都會失去響應(yīng),等到1000次完成后,你才能看到最后一條直線,程序重新接受響應(yīng)。這就是“算死”,解決的方法就是把繪制動作放在計時器或者線程里面,到下一篇,我們會解決這個問題。

      贊(0)
      分享到: 更多 (0)
      網(wǎng)站地圖   滬ICP備18035694號-2    滬公網(wǎng)安備31011702889846號