精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

用Canvas + WASM畫一個迷宮

開發(fā) 開發(fā)工具
本篇將嘗使用canvas + wasm畫一個迷宮,生成算法主要用到連通集算法,使用wasm主要是為了提升運(yùn)行效率。

[[201540]]

本篇將嘗使用canvas + wasm畫一個迷宮,生成算法主要用到連通集算法,使用wasm主要是為了提升運(yùn)行效率。然后再用一個最短路徑算法找到迷宮的出路,***的效果如下:

1. 用連通集算法生成迷宮

生成迷宮的算法其實(shí)很簡單,假設(shè)迷宮的大小是10 * 10,即這個迷宮有100個格子,通過不斷地隨機(jī)拆掉這100個格子中間的墻,直到可以從***個格子走到***一個格子,也就是說***個格子和***一個格子處于同一個連通集。具體如下操作:

(1)生成100個格子,每個格子都不相通

(2)隨機(jī)選取相鄰的兩個格子,可以是左右相鄰或者是上下相鄰,判斷這兩個格子是不是處于同一個連通集,即能否從其中一個格子走到另外一個格子,如果不能,就拆掉它們中間的墻,讓它們相連,處于同一個連通集。

(3)重復(fù)第二步,直到***個格子和***一個格子相連。

那這個連通集應(yīng)該怎么表示呢?我們用一個一維數(shù)組來表示不同的已連通的集合,初始化的時候每個格子的值都為-1,如下圖所示,假設(shè)迷宮為3 * 3,即有9個格子:

每個索引在迷宮的位置:

負(fù)數(shù)表示它們是不同的連通集,因?yàn)槲覀冞€沒開始拆墻,所以一開始它們都是獨(dú)立的。

現(xiàn)在把3、4中間的墻拆掉,也就是說讓3和4連通,把4的值置成3,表示4在3這個連通集,3是它們的根,如下圖所示:

再把5、8給拆了:

再把4、5給拆了:

這個時候3、4、5、8就處于同一個連通集了,但是0和8依舊是兩個不同的連通集,這個時候再把3和0中間的墻給拆了:

由于0的連通集是3,而8的連通集也是3,即它們處于同一個連通集,因此這個時候從***個格子到***一個格子的路是相通的,就生成了一個迷宮。

我們用UnionSet的類表示連通集,如下代碼所示:

  1. class UnionSet{ 
  2.     constructor(size){ 
  3.         this.set = new Array(size); 
  4.         for(var i = this.set.length - 1; i >= 0; i--){ 
  5.             this.set[i] = -1; 
  6.         } 
  7.     } 
  8.     union(root1, root2){ 
  9.         this.set[root1] = root2; 
  10.     } 
  11.     findSet(x){ 
  12.         while(this.set[x] >= 0){ 
  13.             x = this.set[x]; 
  14.         } 
  15.         return x; 
  16.     } 
  17.     sameSet(x, y){ 
  18.         return this.findSet(x) === this.findSet(y); 
  19.     } 
  20.     unionElement(x, y){ 
  21.         this.union(this.findSet(x), this.findSet(y)); 
  22.     } 

我們總共用了22行代碼就實(shí)現(xiàn)了一個連通集。上面的代碼應(yīng)該比較好理解,對照上面的示意圖。如findSet函數(shù)得到某個元素所在的set的根元素,而根元素存放的是負(fù)數(shù),只要存放的值是正數(shù)那么它就是指向另一個結(jié)點(diǎn),通過while循環(huán)一層層的往上找直到負(fù)數(shù)。unionElement可以連通兩個元素,先找到它們所在的set,然后把它們的set union一下變成同一個連通集。

現(xiàn)在寫一個Maze,用來控制畫迷宮的操作,它組合一個UnionSet的實(shí)例,如下代碼所示:

  1. class Maze{ 
  2.     constructor(columns, rows, cavans){ 
  3.         this.columns = columns; 
  4.         this.rows = rows
  5.         this.cells = columns * rows
  6.         //存放是連通的格子,{1: [2, 11]}表示第1個格子和第2、11個格子是相通的 
  7.         this.linkedMap = {}; 
  8.         this.unionSets = new UnionSet(this.cells); 
  9.         this.canvas = canvas; 
  10.     } 

Maze構(gòu)造函數(shù)傳三個參數(shù),前兩個是迷宮的列數(shù)和行數(shù),***一個是canvas元素。在構(gòu)造函數(shù)里面初始化一個連通集,作為這個Maze的核心模型,還初始化了一個linkedMap,用來存放拆掉的墻,進(jìn)而提供給canvas繪圖。

Maze類再添加一個生成迷宮的函數(shù),如下代碼所示:

  1. //生成迷宮 
  2. generate(){ 
  3.     //每次任意取兩個相鄰的格子,如果它們不在同一個連通集, 
  4.     //則拆掉中間的墻,讓它們連在一起成為一個連通集 
  5.     while(!this.firstLastLinked()){ 
  6.         var cellPairs = this.pickRandomCellPairs(); 
  7.         if(!this.unionSets.sameSet(cellPairs[0], cellPairs[1])){ 
  8.             this.unionSets.unionElement(cellPairs[0], cellPairs[1]); 
  9.             this.addLinkedMap(cellPairs[0], cellPairs[1]); 
  10.         }    
  11.     }    

生成迷宮的核心邏輯很簡單,在while循環(huán)里面判斷***個是否與***一個格子連通,如果不是的話,則每次隨機(jī)選取兩個相鄰的格子,如果它們不在同一個連通集,則把它們連通一下,同時記錄一下拆掉的墻到linkedMap里面。

怎么隨機(jī)選取兩個相鄰的格子呢?這個雖然沒什么技術(shù)難點(diǎn),但是實(shí)現(xiàn)起來需要動一番腦筋,因?yàn)樵谶吷系母褡記]有完整的上下左右四個相鄰格子,有些只有兩個,有些有三個。筆者是這么實(shí)現(xiàn)的,相對來說比較簡單:

  1. //取出隨機(jī)的兩個挨著的格子 
  2. pickRandomCellPairs(){ 
  3.     var cell = (Math.random() * this.cells) >> 0; 
  4.     //再取一個相鄰格子,0 = 上,1 = 右,2 = 下,3 = 左 
  5.     var neiborCells = [];  
  6.     var row = (cell / this.columns) >> 0, 
  7.         column = cell % this.rows
  8.     //不是***排的有上方的相鄰元素 
  9.     if(row !== 0){  
  10.         neiborCells.push(cell - this.columns); 
  11.     }    
  12.     //不是***一排的有下面的相鄰元素 
  13.     if(row !== this.rows - 1){  
  14.         neiborCells.push(cell + this.columns); 
  15.     }    
  16.     if(column !== 0){  
  17.         neiborCells.push(cell - 1);  
  18.     }    
  19.     if(column !== this.columns - 1){  
  20.         neiborCells.push(cell + 1);  
  21.     }    
  22.     var index = (Math.random() * neiborCells.length) >> 0; 
  23.     return [cell, neiborCells[index]]; 

首先隨機(jī)選一個格子,然后得到它的行數(shù)和列數(shù),接著依次判斷它的邊界情況。如果它不是處于***排,那么它就有上面一排的相鄰格子,如果不是***一排則有下面一排的相鄰格子,同理,如果不是在***列則有左邊的,不是***一列則有右邊的。把符合條件的格子放到一個數(shù)組里面,然后再隨機(jī)取這個數(shù)組里的一個元素。這樣就得到了兩個隨機(jī)的相鄰元素。

另一個addLinkedMap函數(shù)的實(shí)現(xiàn)較為簡單,如下代碼所示:

  1. addLinkedMap(x, y){ 
  2.     if(!this.linkedMap[x]) this.linkedMap[x] = []; 
  3.     if(!this.linkedMap[y]) this.linkedMap[y] = []; 
  4.     if(this.linkedMap[x].indexOf(y) < 0){ 
  5.         this.linkedMap[x].push(y); 
  6.     } 
  7.     if(this.linkedMap[y].indexOf(x) < 0){ 
  8.         this.linkedMap[y].push(x); 
  9.     } 

這樣生成迷宮的核心邏輯基本完成,但是上面連通集的代碼可以優(yōu)化, 一個是findSet函數(shù),可以在findSet的時候把當(dāng)前連通集里的元素的存放值直接改成根元素,這樣就不用形成一條很長的查找鏈,或者說形成一棵很高的樹,可直接一步到位,如下代碼所示:

  1. findSet(x){ 
  2.     if(this.set[x] < 0) return x; 
  3.     return this.set[x] = this.findSet(this.set[x]); 

這段代碼使用了一個遞歸,在查找的同時改變值。

union函數(shù)也可以做一個優(yōu)化,findSet可以把樹的高度改小,但是在沒有改小前的union操作也可以做優(yōu)化,如下代碼所示:

  1. union(root1, root2){ 
  2.     if(this.set[root1] < this.set[root2]){ 
  3.         this.set[root2] = root1; 
  4.     } else { 
  5.         if(this.set[root1] === this.set[root2]){ 
  6.             this.set[root2]--; 
  7.         }    
  8.         this.set[root1] = root2; 
  9.     }    

這段代碼的目的也是為了減少查找鏈的長度或者說減少樹的高度,方法是把一棵比較矮的連通集成為另外一棵比較高的連通集的子樹,這樣兩個連通集,合并起來的高度還是那棵高的。如果兩個連通集的高度一樣,則選取其中一個作為根,另外一棵樹的結(jié)點(diǎn)在查找的時候無疑這些結(jié)點(diǎn)的查找長度要加上1 ,因?yàn)槎嗔艘粋€新的root,也就是說樹的高度要加1,由于存放的是負(fù)數(shù),所以進(jìn)行減減操作。在判斷樹高度的時候也是一樣的,越小就說明越高。

經(jīng)驗(yàn)證,這樣改過之后,代碼執(zhí)行效率快了一半以上。

迷宮生成好之后,現(xiàn)在開始來畫。

2. 用Canvas畫迷宮

先寫一個canvas的html元素,如下代碼所示:

  1. <canvas id="maze" width="800" height="600"></canvas> 

注意canvas的寬高要用width和height的屬性寫,如果用style的話就是拉伸了,會出現(xiàn)模糊的情況。

怎么用canvas畫線呢?如下代碼所示:

  1. var canvas = document.getElementById("maze"); 
  2. var ctx = canvas.getContext("2d"); 
  3. ctx.moveTo(0, 0); 
  4. ctx.lineTo(100, 100); 
  5. ctx.stroke(); 

這段代碼畫了一條線,從(0, 0)到(100, 100),這也是本篇將用到的canvas的3個基礎(chǔ)的api。

上面已經(jīng)得到了一個linkedMap,對于一個3 * 3的表格,把linkedMap打印一下,可得到以下表格。

通過上面的表格可知道,0和3中間是沒有墻,所以在畫的時候0和3中間就不要畫橫線了,3和4也是相連的,它們中間就不要畫豎線了。對每個普通的格子都畫它右邊的豎線和下面的橫線,而對于被拆掉的就不要畫,所以得到以下代碼:

  1. draw(){  
  2.     var linkedMap = this.linkedMap; 
  3.     var cellWidth = this.canvas.width / this.columns, 
  4.         cellHeight = this.canvas.height / this.rows
  5.     var ctx = this.canvas.getContext("2d"); 
  6.     //translate 0.5個像素,避免模糊 
  7.     ctx.translate(0.5, 0.5); 
  8.     for(var i = 0; i < this.cells; i++){ 
  9.         var row = i / this.columns >> 0, 
  10.             column = i % this.columns; 
  11.         //畫右邊的豎線 
  12.         if(column !== this.columns - 1 && (!linkedMap[i] || linkedMap[i].indexOf(i + 1) < 0)){ 
  13.             ctx.moveTo((column + 1) * cellWidth >> 0, row * cellHeight >> 0); 
  14.             ctx.lineTo((column + 1) * cellWidth >> 0, (row + 1) * cellHeight >> 0); 
  15.         } 
  16.         //畫下面的橫線 
  17.         if(row !== this.rows - 1 && (!linkedMap[i] || linkedMap[i].indexOf(i + this.columns) < 0)){ 
  18.             ctx.moveTo(column * cellWidth >> 0, (row + 1) * cellHeight >> 0); 
  19.             ctx.lineTo((column + 1) * cellWidth >> 0, (row + 1) * cellHeight >> 0); 
  20.         } 
  21.     } 
  22.     //***再一次性stroke,提高性能 
  23.     ctx.stroke(); 
  24.     //畫迷宮的四條邊框 
  25.     this.drawBorder(ctx, cellWidth, cellHeight); 

上面的代碼也比較好理解,在畫右邊的豎線的時候,先判斷它和右邊的格子是否相通,即linkMap[i]里面有沒有i + 1元素,如果沒有,并且它不是***一列,就畫右邊的豎線。因?yàn)槊詫m的邊框放到后面再畫,它比較特殊,***一個格子的豎線是不要畫的,因?yàn)樗敲詫m的出口。每次moveTo和lineTo的位置需要計算一下。

注意上面的代碼做了兩個優(yōu)化,一個是先translate 0.5個像素,為了讓canvas畫線的位置剛好在像素上面,因?yàn)槲覀兊膌ineWidth是1,如果不translate,那么它畫的位置如下圖中間所示,相鄰兩個像素占了半個像素,顯示器顯示的時候變會變虛,而translate 0.5個像素之后,它就會剛好畫在像在像素點(diǎn)上。詳見:HTML5 Canvas – Crisp lines every time

第二個優(yōu)化是所有的moveTo和lineTo都完成之后再stroke,這樣它就是一條線,可以極大地提高畫圖的效率。這個很重要,剛開始的時候沒這么做,導(dǎo)致格子數(shù)稍多的時候就畫不了了,改成這樣之后,繪制的效率提升很多。

我們還可以再做一個優(yōu)化,就是使用雙緩存技術(shù),在畫的時候別直接畫到屏幕上,而是先在內(nèi)存的畫布里面完成繪制,***再一次性地Paint繪制到屏幕上,這樣也可以提高性能。如下代碼所示:

  1. draw(){ 
  2.     var canvasBuffer = document.createElement("canvas"); 
  3.     canvasBuffer.width = this.canvas.width; 
  4.     canvasBuffer.height = this.canvas.height; 
  5.     var ctx = canvasBuffer.getContext("2d"); 
  6.     ctx.translate(0.5, 0.5); 
  7.     for(var i = 0; i < this.cells; i++){ 
  8.   
  9.     }    
  10.     ctx.stroke(); 
  11.     this.drawBorder(ctx, cellWidth, cellHeight); 
  12.     console.log("draw"); 
  13.     this.canvas.getContext("2d").drawImage(canvasBuffer, 0, 0);  

先動態(tài)創(chuàng)建一個canvas節(jié)點(diǎn),獲取它的context,在上面畫圖,畫好之后再用原先的canvas的context的drawImage把它畫到屏幕上去。

然后就可以寫驅(qū)動代碼了,如下畫一個50 * 50的迷宮,并統(tǒng)計一下時間:

  1. const column = 50, 
  2.       row = 50; 
  3. var canvas = document.getElementById("maze"); 
  4. var maze = new Maze(column, row, canvas); 
  5.   
  6. console.time("generate maze"); 
  7. maze.generate(); 
  8. console.timeEnd("generate maze"); 
  9. console.time("draw maze"); 
  10. maze.draw(); 
  11. console.timeEnd("draw maze"); 

畫出的迷宮:

運(yùn)行時間:


可以看到畫一個2500規(guī)模的迷宮,draw的時間還是很少的,而生成的時間也不長,但是我們發(fā)現(xiàn)一個問題,就是迷宮的有些格子是封閉的:

這些不能夠進(jìn)去的格子就沒用了,這不太符合迷宮的特點(diǎn)。所以不能存在自我封閉的格子,由于生成的時候是判斷***個格子有沒有和***一個連通,現(xiàn)在改成***個格子和所有的格子都是連通的,也就是說可以從***個格子到達(dá)任意一個格子,這樣迷宮的誤導(dǎo)性才比較強(qiáng),如下代碼所示:

  1. linkedToFirstCell(){ 
  2.     for(var i = 1; i < this.cells; i++){ 
  3.         if(!this.unionSets.sameSet(0, i))  
  4.             return false
  5.     }    
  6.     return true

把while的判斷也改一下,這樣改完之后,生成的迷宮變成了這樣:

這樣生成的迷宮看起來就正常多了,生成迷宮的時間也相應(yīng)地變長:


但是我們發(fā)現(xiàn)還是有一些比較奇怪的格子布局,如下圖所示:

因?yàn)檫@樣布局的其實(shí)沒太大的意義,如果讓你手動設(shè)計一個迷宮,你肯定也不會設(shè)計這樣的布局。所以我們的算法還可以再改進(jìn),由于上面是隨機(jī)選取兩個相鄰格子,可以把它改成隨機(jī)選取4個相鄰的格子,這樣生成的迷宮通道就會比較長,像上圖這種比較奇芭的情況就會比較少。讀者可以親自動手試驗(yàn)一下。

3. 用最短路徑算法找到迷宮的出路

這個模型更為常見的場景是,現(xiàn)在我在A城鎮(zhèn),準(zhǔn)備去Z城鎮(zhèn),中間要繞道B、C、D等城鎮(zhèn),并且有多條路線可選,并且知道每個城鎮(zhèn)和它連通的城鎮(zhèn)以及兩兩之間距離,現(xiàn)在要求解一條A到Z的最短的路,如下圖所示:

在迷宮的模型里面也是類似的,要求解從***個格子到***一個格子的最短路徑,并且已經(jīng)知道格子之間的連通情況。不一樣的是相鄰格子之間的距離是無權(quán)的,都為1,所以這個處理起來會更加簡單。

用一個貪婪算法可以解決這個問題,假設(shè)從A到Z的最短路徑為A->C->G->Z,那么這條路徑也是A到G、A到C的最短路徑,因?yàn)槿绻鸄到G還有更短的路徑,那么A到Z的距離就還可以更短了,即這條路徑不是最短的。因此我們從A開始延伸,一步步地確定A到其它地點(diǎn)的最短路徑,直到擴(kuò)散到Z。

在無權(quán)的情況下,如上面任意相鄰城鎮(zhèn)的距離相等,和A直接相連的節(jié)點(diǎn)必定是A到這個節(jié)點(diǎn)的最短路徑,如上圖A到B、C、F的最短路徑為A->B、A->C、A->F,這三個點(diǎn)的最短路徑可標(biāo)記為已知。和C直接相鄰的是G和D,C是最短的,所以A->C-G和A->C->D也是最短的,再往下一層,和G、D直接相連的分別是E和Z,所以A->C->G->Z和A->C->D->E是到Z和E的一條最短路徑,到此就找到了A->Z的最短路線。E也可以到Z,但是由于Z已經(jīng)被標(biāo)為已知最短了,所以通過E的這條路徑就被放棄了。

和A直接相連的做為***層,而和***層直接相連的做為第二層,由***層到第二層一直延伸目標(biāo)結(jié)點(diǎn),先被找到的節(jié)點(diǎn)就會被標(biāo)記為已知。這是一個廣度優(yōu)先搜索。

而在有權(quán)的情況下,剛開始的時候A被標(biāo)記為已知,由于A和C是最短的,所以C也被標(biāo)記為已知,B和F不會標(biāo)記,但是它們和A的距離會受到更新,由初始化的無窮大更新為A->B和A->F的距離。在已查找到但未標(biāo)記的兩個點(diǎn)里面,A->F的距離是最短的,所以F被標(biāo)記為已知,這是因?yàn)槿绻嬖诹硗庖粭l更短的未知的路到F,它必定得先經(jīng)過已經(jīng)查找到的點(diǎn)(因?yàn)橐呀?jīng)查找過的點(diǎn)是A的必經(jīng)之路),這里面已經(jīng)是最短的了,所以不可能還有更短的了。F被標(biāo)記為已知之后和F直接相連的E的距離得到更新,同樣地,在已查找到但未標(biāo)記的點(diǎn)里面B的距離最短,所以B被標(biāo)記為已知,然后再更新和B相連的點(diǎn)的距離。重復(fù)這個過程,直到Z被標(biāo)記為已知。

標(biāo)記起始點(diǎn)為已知,更新表的距離,再標(biāo)記表里最短的距離為已知,再更新表的距離,重復(fù)直到目的點(diǎn)被標(biāo)記,這個算法也叫Dijkstra算法。

現(xiàn)在來實(shí)現(xiàn)一個無權(quán)的最短路徑,如下代碼所示:

  1. calPath(){ 
  2.     var pathTable = new Array(this.cells); 
  3.     for(var i = 0; i < pathTable.length; i++){ 
  4.         pathTable[i] = {known: false, prevCell: -1}; 
  5.     }    
  6.     pathTable[0].known = true
  7.     var map = this.linkedMap; 
  8.     //用一個隊(duì)列存儲當(dāng)前層的節(jié)點(diǎn),先進(jìn)隊(duì)列的結(jié)點(diǎn)優(yōu)先處理 
  9.     var unSearchCells = [0]; 
  10.     var j = 0; 
  11.     while(!pathTable[pathTable.length - 1].known){ 
  12.         while(unSearchCells.length){ 
  13.             var cell = unSearchCells.pop(); 
  14.             for(var i = 0; i < map[cell].length; i++){ 
  15.                 if(pathTable[map[cell][i]].known) continue
  16.                 pathTable[map[cell][i]].known = true
  17.                 pathTable[map[cell][i]].prevCell = cell;  
  18.                 unSearchCells.unshift(map[cell][i]); 
  19.                 if(pathTable[pathTable.length - 1].known) break; 
  20.             }    
  21.         }    
  22.     }    
  23.     var cell = this.cells - 1; 
  24.     var path = [cell]; 
  25.     while(cell !== 0){  
  26.         var cell = pathTable[cell].prevCell; 
  27.         path.push(cell); 
  28.     }    
  29.     return path; 

這個算法實(shí)現(xiàn)的關(guān)鍵在于用一個隊(duì)列存儲未處理的結(jié)點(diǎn),每處理一個結(jié)點(diǎn)時,就把和這個結(jié)點(diǎn)相連的點(diǎn)入隊(duì),這樣新入隊(duì)的結(jié)點(diǎn)就會排到當(dāng)前層的結(jié)點(diǎn)的后面,當(dāng)把***層的結(jié)點(diǎn)處理完了,就會把第二層的結(jié)點(diǎn)都push到隊(duì)尾,同理當(dāng)把第二層的結(jié)點(diǎn)都出隊(duì)了,就會把第三層的結(jié)點(diǎn)推到隊(duì)尾。這樣就實(shí)現(xiàn)了一個廣度優(yōu)先搜索。

在處理每個結(jié)點(diǎn)需要需要先判斷一下當(dāng)前結(jié)點(diǎn)是否已被標(biāo)記為known,如果是的話就不用處理了。

在pathTable表格里面用一個prevCell記錄到這個結(jié)點(diǎn)的上一個結(jié)點(diǎn)是哪個,為了能夠從目的結(jié)點(diǎn)一直往前找到到達(dá)***個結(jié)點(diǎn)的路徑。***找到這個path返回。

只要有這個path,就能夠計算位置畫出路徑的圖,如下圖所示:

這個算法的速度還是很快的,如下圖所示:

當(dāng)把迷宮的規(guī)模提高到200 * 200時:

生成迷宮的時間就很耗時了,花費(fèi)了10秒:

于是想著用WASM提高生成迷宮的效率,看看能提升多少。我在《WebAssembly與程序編譯》這篇里已經(jīng)介紹了WASM的一些基礎(chǔ)知識,本篇我將用它來生成迷宮。

4. 用WASM生成迷宮

我在《WebAssembly與程序編譯》提過用JS寫很難編譯,所以本篇也直接用C來寫。上面是用的class,但是WASM用C寫沒有class的類型,只支持基本的操作。但是可以用一個struct存放數(shù)據(jù),函數(shù)名也相應(yīng)地做修改,如下代碼所示:

  1. struct Data{ 
  2.     int *set
  3.     int columns; 
  4.     int rows
  5.     int cells; 
  6.     int **linkedMap; 
  7. } data; 
  8.   
  9. void Set_union(int root1, int root2){ 
  10.     int *set = data.set
  11.     if(set[root1] < set[root2]){ 
  12.         set[root2] = root1; 
  13.     } else { 
  14.         if(set[root1] == set[root2]){ 
  15.             set[root2]--; 
  16.         } 
  17.         set[root1] = root2; 
  18.     } 
  19.   
  20. int Set_findSet(int x){ 
  21.     if(data.set[x] < 0) return x; 
  22.     else return data.set[x] = Set_findSet(data.set[x]); 

數(shù)據(jù)類型都是強(qiáng)類型的,函數(shù)名以類名Set_開頭,類的數(shù)據(jù)放在一個struct結(jié)構(gòu)里面。主要導(dǎo)出函數(shù)為:

  1. #include <emscripten.h> 
  2.   
  3. EMSCRIPTEN_KEEPALIVE //這個宏表示這個函數(shù)要作為導(dǎo)出的函數(shù) 
  4. int **Maze_generate(int columns, int rows){ 
  5.     Maze_init(columns, rows); 
  6.     Maze_doGenerate(); 
  7.     return data.linkedMap; 
  8.     //return Maze_getJSONStr();  

傳進(jìn)來列數(shù)和行數(shù),返回一個二維數(shù)組。其它代碼相應(yīng)地改成C代碼,這里不再放出來。需要注意的是,由于這里用到了一些C內(nèi)置的庫,如使用隨機(jī)數(shù)函數(shù)rand(),所以不能用上文提到的生成wasm的方法,不然會報rand等庫函數(shù)沒有定義。

把生成wasm的命令改成:

emcc maze.c -Os -s WASM=1 -o maze-wasm.html

這樣它會生成一個maze-wasm.js和maze-wasm.wasm(生成的html文件不需要用到),生成的JS文件是用來自動加載和導(dǎo)入wasm文件的,在html里面引入這個JS:

  1. <script src="maze-wasm.js"></script> 
  2. <script src="maze.js"></script> 

它就會自動去加載maze-wasm.wasm文件,同時會定義一個全局的Module對象,在wasm文件加載好之后會觸發(fā)onInit,所以調(diào)它的api添加一個監(jiān)聽函數(shù),如下代碼所示:

  1. var maze = new Maze(column, row, canvas); 
  2. Module.addOnInit(function(){ 
  3.     var ptr = Module._Maze_generate(column, row); 
  4.     maze.linkedMap = readInt32Array(ptr, column * row); 
  5.     maze.draw(); 
  6. }); 

有兩種方法可以得到導(dǎo)出的函數(shù),一種是在函數(shù)名前面加_,如Module._Maze_generate,第二種是使用它提供的ccall或cwrap函數(shù),如ccall:

  1. var linkedMapPtr = Module.ccall("Maze_generate""number",  
  2.                 ["number""number"], [column, row]); 

***個參數(shù)表示函數(shù)名,第二個返回類型,第三個參數(shù)類型,第四個傳參,或者用cwrap:

  1. var mazeGenerate = Module.cwrap("Maze_generate""number",  
  2.                         ["number""number"]); 
  3. var linkedMapPtr = mazeGenerate(column, row); 

三種方法都會返回linkedMap的指針地址,可通過Module.get得到地址里面的值,如下代碼所示:

  1. function readInt32Array(ptr, length) { 
  2.     var linkedMap = new Array(length); 
  3.     for(var i = 0; i < length; i++) { 
  4.         var subptr = Module.getValue(ptr + (i * 4), 'i32'); 
  5.         var neiborcells = []; 
  6.         for(var j = 0; j < 4; j++){ 
  7.             var value = Module.getValue(subptr + (j * 4), 'i32'); 
  8.             if(value !== -1){ 
  9.                 neiborcells.push(value, 'i32'); 
  10.             } 
  11.         } 
  12.         linkedMap[i] = neiborcells; 
  13.     } 
  14.   return linkedMap; 

由于它是一個二維數(shù)組,所以數(shù)組里面存放的是指向數(shù)組的指針,因此需要再對這些指針再做一次get操作,就可以拿到具體的值了。如果取出的值是-1則表示不是有效的相鄰元素,因?yàn)镃里面數(shù)組的長度是固定的,無法隨便動態(tài)push,因此我在C里面都初始化了4個,因?yàn)橄噜徳刈疃嘀挥?個,初始時用-1填充。取出非-1的值push到JS的數(shù)組里面,得到一個用WASM計算的linkedMap. 然后再用同樣的方法去畫地圖。

***再比較一下WASM和JS生成迷宮的時間。如下代碼所示,運(yùn)行50次:

  1. var count = 50; 
  2. console.time("JS generate maze"); 
  3. for(var i = 0; i < count; i++){ 
  4.     var maze = new Maze(column, row, canvas); 
  5.     maze.generate(); 
  6. console.timeEnd("JS generate maze"); 
  7.   
  8. Module.addOnInit(function(){ 
  9.     console.time("WASM generate maze"); 
  10.     for(var i = 0; i < count; i++){ 
  11.         var maze = new Maze(column, row, canvas); 
  12.         var ptr = Module._Maze_generate(column, row); 
  13.         var linkedMap = readInt32Array(ptr, column * row); 
  14.     } 
  15.     console.timeEnd("WASM generate maze"); 
  16. }) 

迷宮的規(guī)模為50 * 50,結(jié)果如下:

可以看到,WASM的時間大概快了25%,并且有時候會觀察到WASM的時間甚至要比JS的時間要長,這時因?yàn)樗惴ㄊ请S機(jī)的,有時候拆掉的墻可能會比較多,所以偏差會比較大。但是大部份情況下的25%還是可信的,因?yàn)槿绻央S機(jī)選取的墻保存起來,然后讓JS和WASM用同樣的數(shù)據(jù),這個時間差就會固定在25%,如下圖所示:

這個時間要比上面的大,因?yàn)楸4媪艘粋€需要拆的墻比較多的數(shù)組。理論上不用產(chǎn)生隨機(jī)數(shù),時間會更少,不過我們的重點(diǎn)是比較它們的時間差,結(jié)果是不管運(yùn)行多少次,時間差都比較穩(wěn)定。

所以在這個例子里面WASM節(jié)省了25%的時間,雖然提升不是很明顯,但還是有效果,很多個25%累積起來還是挺長的。

綜上,本文用JS和WASM使用連通集算法生成迷宮,并用最短路徑算法求解迷宮的路徑。使用WASM在生成迷宮的例子里面可以提升25%的速度。

雖然迷宮小時候就已經(jīng)在玩了,不是什么高大上的東西,但是通過這個例子討論到了一些算法,還用到了很出名的最短路徑算法,還把WASM實(shí)際地應(yīng)用了一遍,作為學(xué)習(xí)的的模型還是挺好的。更多的算法可參考這篇《我接觸過的前端數(shù)據(jù)結(jié)構(gòu)與算法》。

【本文是51CTO專欄作者“人人網(wǎng)FED”的原創(chuàng)稿件,轉(zhuǎn)載請通過51CTO聯(lián)系原作者獲取授權(quán)】

戳這里,看該作者更多好文

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2017-08-28 15:11:41

PythonJavaPHP

2022-03-07 09:20:00

JavaScripThree.jsNFT

2021-09-12 17:23:57

canvas動畫函數(shù)

2023-08-28 17:03:41

CSS 漸變線性漸變

2020-05-09 09:59:52

Python數(shù)據(jù)土星

2011-10-21 09:10:12

JavaScript

2012-01-04 13:55:23

Canvas

2016-03-01 14:37:47

華為

2020-12-20 10:07:57

Canvas圖形驗(yàn)證碼javascript

2021-07-12 14:35:26

代碼架構(gòu)云原生

2014-04-14 15:54:00

print()Web服務(wù)器

2023-04-10 14:20:47

ChatGPTRESTAPI

2022-12-22 08:22:17

Python圖像圖像處理

2021-06-24 06:00:51

EleventyJavaScript靜態(tài)網(wǎng)站

2019-08-14 16:56:38

Python職責(zé)模式請假

2023-05-10 08:05:41

GoWeb應(yīng)用

2009-07-25 17:24:45

2022-03-24 14:42:19

Python編程語言

2023-04-07 15:45:13

Emojicode開源編碼語言

2013-04-08 10:54:51

Javascript
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

欧美不卡一区二区三区四区| 久久欧美中文字幕| 欧美极品第一页| 日本免费福利视频| 78精品国产综合久久香蕉| 中文字幕一区二区三区不卡在线 | 国内免费精品视频| 精品国产123区| 日韩三级中文字幕| 十八禁视频网站在线观看| 高清全集视频免费在线| 91在线精品秘密一区二区| 国产精品人成电影在线观看| 国产亚洲精品久久久久久无几年桃| 免费成人结看片| 日韩三级视频在线看| 国产精品免费成人| 黄色影院在线看| 国产精品日日摸夜夜摸av| 国产亚洲精品美女久久久m| 在线观看黄色网| 免费日韩av片| 精品综合久久久久久97| 少妇无套高潮一二三区| 精品自拍偷拍| 欧美一卡2卡3卡4卡| 国产视频一区二区视频| 91超碰在线播放| 亚洲免费看黄网站| 一本久道久久综合狠狠爱亚洲精品| 午夜影院在线视频| 国产精品白丝av| 国产日韩精品在线| 日韩中文字幕高清| 亚洲欧美网站| 久久久久国产精品www| 我要看黄色一级片| 四虎国产精品免费观看| 国产亚洲人成a一在线v站| 波多野结衣一二三区| 日韩区一区二| 337p亚洲精品色噜噜噜| 99热一区二区| 成人一级视频| 欧美色综合久久| 久久久久久久久久久久久久国产| 国产美女高潮在线| 亚洲成人自拍一区| 黄色一级片av| av免费在线观看网站| 亚洲少妇屁股交4| 艳母动漫在线免费观看| 免费不卡视频| **网站欧美大片在线观看| 伊人久久婷婷色综合98网| se在线电影| 国产精品每日更新| 一区二区av| 欧美三级黄网| 亚洲理论在线观看| 日韩国产小视频| 男女视频在线| 精品国产福利在线| www.亚洲天堂网| 成人性教育av免费网址| 色婷婷久久综合| wwwwww.色| 亚洲欧美专区| 精品国一区二区三区| 无码任你躁久久久久久老妇| 日韩理论电影中文字幕| 亚洲欧美一区二区激情| 99久久99久久精品免费| 天天综合精品| 国内精品久久久久| 国产成人精品777777| 麻豆成人免费电影| 亚洲在线一区二区| 五月婷婷狠狠干| 亚洲国产精品激情在线观看 | eeuss影院www在线观看| 中文字幕一区二区三中文字幕| 免费观看国产视频在线| 91白丝在线| 欧美在线观看一区二区| 亚洲成人手机在线观看| 国产成人在线中文字幕| 亚洲人成绝费网站色www| 国产精品一区二区亚洲| 国产精品va| 国产成人精品电影| 国产人妻精品一区二区三区| 成人18精品视频| 日韩国产在线一区| 日本高清视频在线观看| 亚洲国产精品久久一线不卡| 麻豆传传媒久久久爱| 国产精区一区二区| 亚洲欧美日韩在线高清直播| 久久国产高清视频| 国产精品日韩欧美一区| 成人国产精品日本在线| 欧洲综合视频| 亚洲激情六月丁香| 免费黄色一级网站| 国产精品tv| xxxx性欧美| 五月天激情四射| 国产激情精品久久久第一区二区| 欧美久久久久久久| 国产后进白嫩翘臀在线观看视频| 91国偷自产一区二区使用方法| 野花视频免费在线观看| 日韩精品一区二区久久| 午夜精品一区二区三区在线视| 一级黄色片视频| 久久嫩草精品久久久久| 黄色三级中文字幕| 亚洲精品毛片| 亚洲天堂男人天堂| 欧美福利视频一区二区| 国产一区欧美二区| 日韩和欧美的一区二区| 夜鲁夜鲁夜鲁视频在线播放| 欧美成人乱码一区二区三区| 欧美性生交大片| 日韩专区一卡二卡| 蜜桃传媒视频麻豆第一区免费观看| 午夜小视频在线观看| 欧美午夜影院一区| av男人的天堂av| 午夜在线视频观看日韩17c| 99久久99久久精品国产片| 色影院视频在线| 欧美午夜精品理论片a级按摩| 99在线免费视频观看| 国产高清一区二区三区| 欧洲日本亚洲国产区| 午夜精品久久久久久久99热浪潮| 国产一区二区三区毛片| 欧美日韩视频在线观看一区二区三区 | 夜夜爽妓女8888视频免费观看| 国产综合色产在线精品 | 99tv成人| 国产在线久久久| 在线观看a视频| 欧美视频中文一区二区三区在线观看| 日韩精品免费专区| 国产乱码精品一区二三赶尸艳谈| 91在线视频18| 黄色国产一级视频| 57pao国产一区二区| 欧美成人免费大片| www.蜜桃av.com| 亚洲精品久久嫩草网站秘色| 成人在线短视频| 综合天堂av久久久久久久| 91久久精品在线| 超鹏97在线| 日韩精品一区二区三区中文不卡 | 国产成人一区二区三区| 黄网在线观看| 欧美午夜免费电影| 九九热久久免费视频| 蜜桃视频在线观看一区| 亚洲欧美日韩国产yyy| 欧美成人黄色| 欧美成人精品影院| 刘亦菲毛片一区二区三区| 午夜精品久久久久久久99樱桃| 在线观看国产网站| 日韩高清国产一区在线| 中文视频一区视频二区视频三区| 国产亚洲久久| 2023亚洲男人天堂| av在线资源站| 日韩亚洲欧美中文三级| 国产做受高潮漫动| 欧美亚洲国产精品久久| 综合久久久久久| 亚洲一级免费观看| 欧美激情综合色综合啪啪| 国产一区二区久久久| se01亚洲视频| 久久成人在线视频| 同心难改在线观看| 欧美三级一区二区| 久久久久久久九九九九| 国产亚洲欧美激情| 中文字幕 欧美 日韩| 久久天天综合| 国产91在线亚洲| 欧美男gay| 亚洲一区二区三区在线视频| 中文av在线全新| 久久精品国产v日韩v亚洲 | 亚洲欧美日韩天堂| 99国产成人精品| 日韩欧美999| 永久看片925tv| 久久精品亚洲国产奇米99| 久久久久xxxx| 久久国产主播| 精品久久久无码人妻字幂| 精品盗摄女厕tp美女嘘嘘| 国产精品一区二区a| 免费视频成人| 欧美夜福利tv在线| 在线三级电影| 少妇av一区二区三区| 四虎影院在线播放| 欧美videos大乳护士334| а中文在线天堂| 香蕉成人伊视频在线观看| 国产精品夜夜夜爽阿娇| 久久久国产精品不卡| 性猛交╳xxx乱大交| 精品亚洲porn| 超碰影院在线观看| 最新国产乱人伦偷精品免费网站| 国产91av视频在线观看| 精品久久国产| 久久精品国产美女| 99香蕉久久| 91成人伦理在线电影| 国产精品原创视频| 国产成人一区二区三区| 亚洲精品中文字幕| 国内成人精品一区| 丝袜美腿av在线| 久久精品亚洲一区| www.久久热.com| 亚洲午夜av久久乱码| 亚洲区小说区图片区| 精品久久国产字幕高潮| jizz中国少妇| 在线不卡一区二区| 在线观看xxxx| 欧美日韩精品欧美日韩精品一综合| 日本高清不卡码| 欧美色图在线视频| 国产a∨精品一区二区三区仙踪林| 一区二区三区四区视频精品免费| 日韩视频中文字幕在线观看| 中文字幕日韩欧美一区二区三区| 国产亚洲精品精品精品| 国产精品美女久久久久aⅴ| 日韩欧美黄色网址| 欧美激情中文字幕| 波多野结衣一二三四区| 国产精品午夜在线| 天堂av网手机版| 亚洲欧洲精品成人久久奇米网| 久久久精品成人| 精品中文字幕一区二区三区av| 米奇777在线欧美播放| 一区在线免费| 日产精品99久久久久久| 日本在线播放一二三区| 欧美大尺度激情区在线播放| 在线观看的网站你懂的| 色综合久久中文字幕综合网小说| 秋霞在线午夜| 97视频在线观看网址| 欧美大胆a人体大胆做受| 欧美一级淫片videoshd| 日本免费久久| 91精品久久久久久久久中文字幕| 亚洲综合资源| 成人精品一二区| 欧美人成在线观看ccc36| 欧美成人一区二区在线| 欧美日韩激情在线一区二区三区| 中文视频一区视频二区视频三区| 牛牛国产精品| 久久久免费视频网站| 青青青爽久久午夜综合久久午夜| 一级黄色片国产| 不卡视频在线看| 鲁丝一区二区三区| 亚洲欧美日韩小说| 亚洲 欧美 视频| 欧美色老头old∨ideo| www.av黄色| 亚洲网站在线看| 日本aa在线| 国产精品久久久| 亚洲国产欧美在线观看| 欧美精品成人一区二区在线观看 | 欧美伊人亚洲伊人色综合动图| 91网免费观看| 九九免费精品视频在线观看| 亚洲第一综合网站| 香蕉精品999视频一区二区| jizz18女人| youjizz久久| 91n在线视频| 欧美日韩性生活视频| 一区二区 亚洲| 日韩精品一二三四区| 麻豆传媒在线完整视频| 91精品成人久久| 成年永久一区二区三区免费视频| 久久婷婷国产综合尤物精品| 国产精品伦理久久久久久| 免费欧美一级视频| 国产乱一区二区| av男人的天堂av| 亚洲国产综合人成综合网站| 中文字幕免费观看视频| 亚洲老头老太hd| 97av自拍| 大胆国模一区二区三区| 欧美日韩一区综合| 亚洲高清不卡| 原创真实夫妻啪啪av| 中文字幕欧美激情一区| 成人毛片18女人毛片| 精品美女一区二区| 国产写真视频在线观看| 国产精品欧美在线| 国产午夜一区| 欧美丰满熟妇bbbbbb百度| 国产成人免费视频精品含羞草妖精| 欧美xxxooo| 色综合色综合色综合色综合色综合| 欧美深深色噜噜狠狠yyy| 北条麻妃在线| 97精品一区二区视频在线观看| 爱情电影网av一区二区| 日韩精品最新在线观看| 久久av一区二区三区| 精品黑人一区二区三区观看时间| 亚洲高清视频的网址| 亚洲第一精品网站| 欧美激情videoshd| 日本精品视频| 天天想你在线观看完整版电影免费| 久久国产人妖系列| 国产精品久久国产精麻豆96堂| 欧美中文字幕一二三区视频| 免费福利在线观看| 热久久视久久精品18亚洲精品| 久久久久97| 日韩精品―中文字幕| 不卡一区中文字幕| 日韩精品在线免费视频| 精品视频在线观看日韩| 成人勉费视频| 色涩成人影视在线播放| 美女精品一区二区| www.黄色com| 91精品久久久久久蜜臀| 最爽无遮挡行房视频在线| av色综合网| 一区二区动漫| 成人精品999| 欧美色视频一区| www在线视频| 国产传媒一区二区三区| 9国产精品视频| 免费在线观看你懂的| 色欧美片视频在线观看在线视频| 成年人免费在线视频| 国产一区二区香蕉| 欧美精品色网| 亚洲第九十七页| 欧美三级视频在线播放| 欧美激情视频在线播放| 3d精品h动漫啪啪一区二区| 影音先锋在线一区| 摸摸摸bbb毛毛毛片| 欧美日韩免费视频| 欧美人与牲禽动交com| 精品蜜桃一区二区三区| 日本系列欧美系列| www青青草原| 国产视频综合在线| 亚洲伊人精品酒店| 免费一级特黄特色毛片久久看| 久久先锋资源网| 一区二区美女视频| 午夜伦理精品一区| 丝袜连裤袜欧美激情日韩| 日本超碰在线观看| 亚洲一区二区三区精品在线| 黄色av免费在线观看| 亚洲一区二区三区毛片| 国产欧美成人| 性生交大片免费全黄| 亚洲激情视频在线播放| 香蕉久久久久久| 北条麻妃在线视频观看| 亚洲欧美另类小说| 国产在线资源| 国产厕所精品在线观看| 日本亚洲最大的色成网站www| 久久久久久久蜜桃| 最近中文字幕2019免费| 精品人人人人|