整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          30 個純 HTML5 實(shí)現(xiàn)的游戲

          覽器和 JavaScript 的功能逐年不斷的變強(qiáng)變大。曾幾何時,任何類型的游戲都需要Flash。但隨著 HTML5 發(fā)展,HTML5 + WebGL 游戲式就慢慢占領(lǐng)著這個舞臺。以下是30款流行的游戲,它們可以在所有現(xiàn)代瀏覽器中運(yùn)行,并且只使用web技術(shù)構(gòu)建。

          1. HexGL

          地址:http://hexgl.bkcore.com/

          類型:街機(jī),賽車


          HexGL 是一款基于HTML5,JavaScript和WebGL的快節(jié)奏的賽車游戲。玩家可以使用鍵盤,觸摸屏設(shè)備或leap motion(體感控制器)來控制太空飛船。

          2. CrossCode

          地址:http://www.cross-code.com/en/home

          類型:動作,角色扮演

          一個復(fù)古靈感的2D游戲設(shè)定在遙遠(yuǎn)的未來。這是一個充滿偉大的游戲機(jī)制,如組合,拼圖,技能樹,任務(wù),物品等等。

          3. Sketchout

          地址:https://sketch-out.appspot.com/

          類型:街機(jī)

          Sketchout的任務(wù)保護(hù)你的行星,并通過改變流星的方向來消滅對手,通過使流星偏轉(zhuǎn)來保護(hù)您的星球并消滅對方,這款游戲有很棒的視覺效果和音樂特效。

          4. Treasure Arena

          地址:http://www.treasurearena.com/類型:多人,角色扮演,動作

          Treasure Arena 是一款動態(tài)的競技場戰(zhàn)斗游戲,最多可容納4名玩家。它具有不同的游戲模式,出色的幀率和配樂,是一個非常有趣的游戲。

          5. Bejeweled

          地址:http://bejeweled.popcap.com/html5/

          類型:街機(jī),解謎,娛樂

          HTML5格式的經(jīng)典“寶石迷陣”游戲。這是一個官方克隆,因此可以正常運(yùn)行且外觀完美。

          6. Missile Game

          地址:http://missile-game.bwhmather.com/類型:街機(jī)

          這是一款非常具有挑戰(zhàn)性的游戲,游戲中我們扮演的是一枚被發(fā)射進(jìn)隧道的導(dǎo)彈。游戲有很酷的黑白圖像,玩的時候會有很強(qiáng)的場景效果。

          7. Gods Will Be Watching

          地址:http://www.deconstructeam.com/games/gods-will-be-watching/類型:拼圖

          在這個令人毛骨悚然(但又很棒)的游戲中,我和自己團(tuán)隊(duì)必須獨(dú)自生存40天。團(tuán)隊(duì)有六名成員,其中包括一只狗,一名精神病醫(yī)生和一個機(jī)器人,您必須與他們互動,以使其保持溫暖,溫飽和理智的狀態(tài)。

          8. Sinuous

          地址:http://www.sinuousgame.com/類型:街機(jī)

          一個簡單的游戲,極簡的圖形和流暢的幀率。拾取電源時避免與紅點(diǎn)碰撞。此外,如果你想要那些額外的積分,就需要不停向前移動

          9. Swooop

          地址:http://swooop.playcanvas.com/類型:街機(jī)

          在一個美麗多彩的3D世界里,到處飛翔,收集寶石和星星。

          10. Free Rider HD

          地址:http://www.freeriderhd.com/

          Free Rider HD 是一款令人上癮的游戲,你可以在其他玩家繪制的賽道上騎自行車。可以在成千上萬的播放器曲目中選擇一個播放,也可以創(chuàng)建自己的曲目并分享。

          11. Entanglement

          地址:http://entanglement.gopherwoodstudios.com/zh-CN-index.html類型:拼圖,娛樂

          這個游戲的目的是通過在網(wǎng)格上放置線段來創(chuàng)建一條盡可能長的路徑。你可以單獨(dú)玩,也可以和朋友一起玩。

          12. Escape from XP

          地址:https://www.modern.ie/en-us/ie6countdown#escape-from-xp

          類型:動作,街機(jī)

          用“Escape from XP”來慶祝 Windows XP 的終結(jié)。你的任務(wù)是拯救最后一個陷入Clippy暴政的開發(fā)人員。

          13. Polycraft

          地址:http://polycraftgame.com/類型:角色扮演,塔防,動作

          在這個很棒的3D游戲中,你到處收集資源,建造東西,完成任務(wù)。關(guān)于它的所有東西都經(jīng)過拋光,并且運(yùn)行也非常順暢。

          14. 2048

          地址:https://gabrielecirulli.github.io/2048/類型:拼圖

          一個非常上癮的游戲,你可能已經(jīng)玩過了。在 2048 ,你移動編號的圖塊并合并它們。當(dāng)界面中最大數(shù)字是`2048 時,游戲勝利。

          15. Onslaught Arena

          地址:http://arcade.lostdecadegames.com/onslaught_arena/

          類型:動作

          一種快節(jié)奏的復(fù)古生存游戲,您可以使用不同的武器與成群的敵人作戰(zhàn)。

          16. Angry Birds

          地址:http://chrome.angrybirds.com/類型:游戲

          《憤怒的小鳥》游戲,這就不用介紹了。

          17. Cube Slam

          地址:https://www.cubeslam.com/mcycrs

          類型:街機(jī),多人

          具有豐富的色彩和炫酷的3D圖形乒乓球游戲。我們可以通過向朋友發(fā)送一個URL來挑戰(zhàn)他們,還可以通過網(wǎng)絡(luò)攝像頭看到對方。

          18. The Wizard

          地址:http://hypnoticowl.com/games/the-wizard/類型:動作,角色扮演,策略

          Wizard 是基于回合的地牢爬行者,在里面會遇到神話般的怪物并找到奇妙的咒語。該游戲具有酷炫的戰(zhàn)斗機(jī)制,有時可能會帶來很大挑戰(zhàn)。

          19. X-Type

          地址:http://phoboslab.org/xtype/類型:動作,街機(jī)

          在這款酷炫的太空射擊游戲中,你目的就是要起戰(zhàn)勝 Boss。

          20. Cookie Clicker

          地址:http://orteil.dashnet.org/cookieclicker/類型:休閑,搞笑

          Cookie clicker 是一款可能為了開玩笑而創(chuàng)建的游戲,但仍然提供了大量的樂趣。你可以從0個cookie開始,然后單擊一些有效率的cookie,最后你可能會發(fā)現(xiàn)自己擁有數(shù)十億個cookie。

          21. Elevator Saga

          地址:http://play.elevatorsaga.com/類型:拼圖,編碼

          這類屬于程序員類型游戲 。在電梯中的任務(wù)是通過對電梯的運(yùn)動進(jìn)行編程,以最有效的方式運(yùn)送人員,這些都是用 JavaScript 來完成的。

          22. Game of Bombs

          地址:http://gameofbombs.com/landing類型:動作,角色扮演,多人

          Game of Bombs是一個轟炸機(jī)類型的游戲,在廣闊地圖上,都有著敵方玩家。收集力量,皮膚和成就,以成為最佳轟炸機(jī)玩家的方式。

          23. Olympia Rising

          地址:http://or.paleozoic.com/類型:平臺游戲,動作

          Olympia Rising具有漂亮復(fù)古外觀圖形的游戲。它坐落在古希臘,在那里我們扮演的女人被賦予了重新的機(jī)會,所以我們的任務(wù)就是逃離死者的世界。

          24. Pixel Race

          地址: https://ned.im/pixel-race-game/類型:街機(jī),賽車

          Pixel Race是一款簡單概念概念,你可以在收集硬幣的同時控制汽車以避開障礙物。如果有足夠的耐心和空閑時間,那么你可能會打破記錄(記錄為36309個硬幣)。

          25. Little Alchemy

          地址:https://littlealchemy.com/類型:拼圖

          從這四個基本元素開始,將它們組合起來,創(chuàng)建510種可能的組合。

          26. Arena5

          地址:http://www.kevs3d.co.uk/dev/arena5/類型:街機(jī)

          在數(shù)字領(lǐng)域中飛行并射擊幾何敵人以獲得高分。

          27.Vector Runner Remix

          地址:https://vector-runner-remix.tresensa.com/

          類型:街機(jī)

          在這個充滿色彩和幾何形狀的平臺游戲中,盡你所能奔跑吧。

          28. Biolab Disaster

          地址:http://playbiolab.com/類型:動作

          一款出色的像素藝術(shù)平臺游戲,你必須在這里逃脫充滿突變生物和其他不良生物的實(shí)驗(yàn)室。

          29. World's Biggest PAC-MAN

          地址:http://worldsbiggestpacman.com/#類型:街機(jī)

          30. New Super Resident Raver

          地址:http://games.jessefreeman.com/new-super-resident-raver/

          從即將到來的僵尸入侵中拯救驚慌失措的人們。收集錢,升級你的武器和戰(zhàn)斗僵尸。


          作者:Danny Markov 來源:tutorialzin 譯者:前端小智

          原文:https://tutorialzine.com/2015/02/30-amazing-games-made-only-with-html5

          單實(shí)現(xiàn)2048小游戲

          想實(shí)現(xiàn)2048游戲書寫代碼時可以分為三個步驟

          一、HTML部分

          先書寫HTML把游戲結(jié)構(gòu)搭建出來

          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
              <link rel="stylesheet" href="index.css">
          </head>
          <body>
          <!--最外部的大框-->
          <div class="outermost"> //包裹游戲全局的大盒子
              <!--title-->
              <span class="top"><b>SCORE:<span id="score01"></span></b></span>//頂部實(shí)時顯示的游戲分?jǐn)?shù)
              <!--游戲大框框-->
              <div class="big">//2048游戲?yàn)樗男兴牧幸虼诵枰?6個div盒子
                  <div class="cell" id="c00"></div>
                  <div class="cell" id="c01"></div>
                  <div class="cell" id="c02"></div>
                  <div class="cell" id="c03"></div>
           
                  <div class="cell" id="c10"></div>
                  <div class="cell" id="c11"></div>
                  <div class="cell" id="c12"></div>
                  <div class="cell" id="c13"></div>
           
                  <div class="cell" id="c20"></div>
                  <div class="cell" id="c21"></div>
                  <div class="cell" id="c22"></div>
                  <div class="cell" id="c23"></div>
           
                  <div class="cell" id="c30"></div>
                  <div class="cell" id="c31"></div>
                  <div class="cell" id="c32"></div>
                  <div class="cell" id="c33"></div>
              //游戲結(jié)束時會彈出的提示框
              </div>
              <!--提示框-->
              <div class="tips" id="gameover">
                 <p>GAME OVER!!! <br>
                     SCORE: <span id="score02">0</span><br>
                     <button class="startbtn">重新開始</button>
                 </p>
              </div>
              <!--重玩一遍-->
              <div class="foot">
                  <button class="replay"><a>重玩一遍</a></button>
              </div>
          </div>
          <script type="text/javascript" src="index.js"></script>
          </body>
          </html>

          二、css部分

          經(jīng)過了第一步的搭建游戲框架,第二部就是給游戲添加樣式,使它能顯示出來

          *{
              padding: 0px;
              margin: 0px auto;
              font-family: Arial;
           
          }
          /*最外部的大框*/
          .outermost{
              width: 480px;
              height: 600px;
              font-size: 40px;
              margin-top: 120px;
          }
          /*title*/
          <!--頂部顯示分?jǐn)?shù)的樣式-->
          .top{
              margin: auto;
          }
          .top span{
              color: red;
          }
          /*游戲大框框*/
          .big{
              width: 480px;
              height: 480px;
              background:pink;
              border-radius: 8px;
          }
          <!--給每一個盒子包裹的小框子添加樣式-->
          .cell{
              list-style: none;
              float: left;
              display: inline-block;
              width: 100px;
              height: 100px;
              line-height: 100px;
              text-align: center;
              background-color: #fbf8cd;
              margin-left: 16px;
              margin-top: 16px;
              border-radius: 6px;
          }
          <!--提前把出現(xiàn)的數(shù)2、4、8、16等的數(shù)所在的格子給添加好樣式增加游戲體驗(yàn)感-->
          .n2{background-color:#f65e3b;color:#776e65}
          .n4{background-color:#33b5e5;color:#776e65}
          .n8{background-color:#f2b179;color:#776e65}
          .n16{background-color:#f59563;color:#776e65}
          .n32{background-color:#f67c5f;color:#776e65}
          .n64{background-color:#f65e3b;color:#776e65}
          .n128{background-color:#edcf72;color:#776e65}
          .n256{background-color:#edcc61;color:#776e65}
          .n512{background-color:#9c0;color:#776e65}
          .n1024{background-color:#33b5e5;color:#776e65;font-size:40px}
          .n2048{background-color:#09c;color:#776e65;font-size:40px}
          /*提示框樣式*/
          .tips{
              border: 1px solid #cccccc;
              background: #FFFFFF;
              width: 400px;
              height: 200px;
              border-radius: 10px;
              color: #ff4456;
              text-align: center;
              line-height: 60px;
              position: absolute;
              top: 50%;
              left: 50%;
              margin-left: -200px;
              margin-top: -100px;
              display: none;
          }
          .tips .startbtn{
              height: 50px;
              width: 200px;
              color: #FFFFFF;
              font-size: 20px;
              line-height: 50px;
              border-radius: 10px;
              background: cornflowerblue;
              border: none;
          }
          /*重玩一遍*/
          .foot{
              width: 200px;
              height: 50px;
          }
          .foot>.replay{
              width: 200px;
              height: 50px;
              background: aquamarine;
              margin-top: 60px;
              color: lightpink;
              border:0;
              font-size: 24px;
              font-weight: bold;
              border-radius: 6px;
          }

          書寫好了HTML+CSS部分游戲的模樣也就出來了,如下圖所示:

          三、JS部分

          下面就到了最后也是最關(guān)鍵的一步----添加行為,也就是JS部分的書寫,給其添加效果

          //創(chuàng)建一個對象,里面存儲所有的游戲數(shù)據(jù)及游戲方法
          var game = {
          	data : [],   //定義一個數(shù)組,用來存所有的游戲的數(shù)據(jù)
          	score : 0,   //定義一個分?jǐn)?shù)的屬性
          	gamerunning : 1,   //定義一個游戲運(yùn)行的狀態(tài),將其設(shè)置為1與其他狀態(tài)區(qū)分開
          	gameover : 0,     //定義一個游戲結(jié)束的狀態(tài)
          	status : 0,      //這個是目前游戲的狀態(tài),時刻的跟上面兩個狀態(tài)做比較,確定游戲處于運(yùn)行或者結(jié)束
          	start : function(){   //游戲開始時候的方法
          //		游戲開始的時候肯定是要把游戲的狀態(tài)設(shè)置成游戲運(yùn)行的狀態(tài)
          //		this == game
          		this.status = this.gamerunning;
          //		游戲開始的時候分?jǐn)?shù)清空
          		this.score = 0;
          //		數(shù)組中的所有元素全部設(shè)置成0
          		this.data = [
          			[0,0,0,0],
          			[0,0,0,0],
          			[0,0,0,0],
          			[0,0,0,0]
          		];
          		this.randomNum();//調(diào)用下面自定義的隨機(jī)函數(shù),可以在移動和開始的時候隨機(jī)出來一個數(shù)
          		this.randomNum();//調(diào)用兩次是因?yàn)檫@是游戲開始時的方法,開局隨機(jī)出現(xiàn)兩個數(shù)和位置,因此需要調(diào)用兩次
          		this.dataView();//調(diào)用下面所寫的更新視圖的方法
          	},
          //	隨機(jī)數(shù)的函數(shù),開始的時候隨機(jī)生成,移動的時候隨機(jī)生成
          	randomNum: function(){
          		while(true){
          			//		隨機(jī)生成行和列 0 - 3隨機(jī)整數(shù)
          			var r = Math.floor( Math.random() * 4 );   //隨機(jī)生成一個行
          			var c = Math.floor( Math.random() * 4 );   //隨機(jī)生成一個列
          			
          			if(this.data[r][c] == 0){
          				var num = Math.random() > 0.5 ? 2 : 4;//隨機(jī)出現(xiàn)2或4
          				this.data[r][c] = num;
          				break;
          			}
          		}
          	},
          //	更新試圖的方法
          	dataView: function(){
          //		大的循環(huán),然后把所有的元素全部遍歷一遍
          		for(var r = 0; r < 4; r++){
          			for(var c = 0; c < 4; c++){
          //				找到對應(yīng)的div
          				var div = document.getElementById("c" + r + c);  //字符串拼接
          				if(this.data[r][c] != 0){
          //					數(shù)組中對應(yīng)的內(nèi)容放到格子上面去
          					div.innerHTML = this.data[r][c];
          //					樣式也寫成對應(yīng)的
          					div.className = "cell n" + this.data[r][c];
          				}else{
          					div.innerHTML = "";
          					div.className = "cell"
          				}
          			}
          		}
          //		更新分?jǐn)?shù)
          		document.getElementById("score01").innerHTML = this.score;
          		//游戲沒有結(jié)束的時候 彈出層時刻都是隱藏的
          		if(this.status == this.gamerunning){
          			document.getElementById("gameover").style.display = "none";
          		}else{
          			document.getElementById("gameover").style.display = "block";
          			document.getElementById("score02").innerHTML = this.score;
          		}
          	},
          //	判斷游戲是否結(jié)束的方法
          	isgameover: function(){
          		for(var r = 0; r < 4; r++){
          			for(var c = 0; c < 4; c++){
          				if(this.data[r][c] == 0){  //里面有空格子的時候,游戲還是可以運(yùn)行
          					return false;   //表示游戲還沒有結(jié)束
          				}
          				if(c < 3){//判斷左右是否有相同的
          					if(this.data[r][c] == this.data[r][c+1]){
          						return false;
          					}
          				}
          				if(r < 3){
          					if(this.data[r][c] == this.data[r+1][c]){
          						return false;
          					}
          				}
          			}
          		}
          		return true;
          	},

          //移動的方法,左 右 上 下四個部分

          //	左 右 上 下
          //	左移的方法
          	moveLeft: function(){
          		var before = String(this.data);   //之前做一次轉(zhuǎn)換
          //		具體的移動需要處理的邏輯,直接處理好每一行即可
          		for(var r = 0;r < 4;r ++){
          			this.moveLeftInRow(r);
          		}
          		var after = String(this.data);  //移動之后再做一次轉(zhuǎn)換
          //		如果說移動之前不等于移動之后,肯定是發(fā)生了移動
          		if(before != after){
          			this.randomNum();   //生成隨機(jī)數(shù)
          //			生成的隨機(jī)數(shù)可能會造成游戲的gameover
          			if(this.isgameover()){
          //				改變游戲的狀態(tài)
          				this.status = this.gameover
          			}
          //			更新視圖
          			this.dataView();
          		}
          	},
          	moveLeftInRow: function(r){   //只去做處理每一行的邏輯
          		for(var c = 0; c < 3; c++){
          			var nextc = this.getNextinRow(r,c);
          			if(nextc != -1){
          				if(this.data[r][c] == 0){
          //					如果等于0,直接替換
          					this.data[r][c] = this.data[r][nextc];
          					this.data[r][nextc] = 0;  //位置恢復(fù)成0
          					c --;   //要讓位置恢復(fù)到原地
          				}else if(this.data[r][c] == this.data[r][nextc]){
          					this.data[r][c] *= 2;   //位置直接翻一倍
          					this.data[r][nextc] = 0;
          					this.score += this.data[r][c];  //更新分?jǐn)?shù)
          				}
          			}else{   //沒有找到
          				break;   //直接退出循環(huán)
          			}
          		}
          	},
          	getNextinRow: function(r,c){
          		for(var i = c + 1; i < 4; i++){
          			if(this.data[r][i] != 0){
          				return i;    //表示已經(jīng)找到位置,并且把位置返回出來
          			}
          		}
          		return -1;   //返回一個標(biāo)識符
          	},
          //	右移的方法
          	moveRight: function(){
          		var before = String(this.data);
          		for(var r = 0; r < 4; r++){
          			this.moveRightInRow(r);
          		}
          		var after = String(this.data);
          		if(before != after){
          			this.randomNum();
          			if(this.isgameover()){
          				this.status = this.gameover;
          			}
          			this.dataView();
          		}
          	},
          	moveRightInRow: function(r){
          		for(var c = 4; c > 0; c--){
          			var prevc = this.getPrevInRow(r,c);
          			if(prevc != -1){
          				if(this.data[r][c] == 0){
          					this.data[r][c] = this.data[r][prevc];
          					this.data[r][prevc] = 0;
          					c ++
          				}else if(this.data[r][c] == this.data[r][prevc]){
          					this.data[r][c] *= 2;
          					this.data[r][prevc] = 0;
          					this.score += this.data[r][c];
          				}
          			}else{
          				break;
          			}
          		}
          	},
          	getPrevInRow: function(r,c){
          		for(var i = c - 1; i >= 0; i--){
          			if(this.data[r][i] != 0){
          				return i;
          			}
          		}
          		return -1;
          	},
          //	上移
          	moveUp: function(){
          		var before = String(this.data);
          		for(var c = 0; c < 4; c++){
          			this.moveUpInCol(c);
          		}
          		var after = String(this.data);
          		if(before != after){
          			this.randomNum();
          			if(this.isgameover()){
          				this.status = this.gameover;
          			}
          			this.dataView();
          		}
          	},
          	moveUpInCol: function(c){
          		for(var r = 0;r < 4; r++){
          			var nextr = this.getNextInCol(r,c);
          			if(nextr != -1){
          				if(this.data[r][c] == 0){
          					this.data[r][c] = this.data[nextr][c];
          					this.data[nextr][c] = 0;
          					r -- ;
          				}else if(this.data[r][c] == this.data[nextr][c]){
          					this.data[r][c] *= 2;
          					this.data[nextr][c] = 0;
          					this.score += this.data[r][c];
          				}
          			}else{
          				break;
          			}
          		}
          	},
          	getNextInCol: function(r,c){
          		for(var i = r + 1; i < 4; i++){
          			if(this.data[i][c] != 0){
          				return i;
          			}
          		}
          		return -1;
          	},
          //	下移的方法
          	moveDown: function(){
          		var before = String(this.data);
          		for(var c = 0;c < 4; c++){
          			this.moveDownInCol(c);
          		}
          		var after = String(this.data);
          		if(before != after){
          			this.randomNum();
          			if(this.isgameover()){
          				this.status = this.gameover;
          			}
          			this.dataView();
          		}
          	},
          	moveDownInCol: function(c){
          		for(var r = 3; r > 0; r--){
          			var prev = this.getPrevIncol(r,c);
          			if(prev != -1){
          				if(this.data[r][c] == 0){
          					this.data[r][c] = this.data[prev][c];
          					this.data[prev][c] = 0;
          					r -- ;
          				}else if(this.data[r][c] == this.data[prev][c]){
          					this.data[r][c] *= 2;
          					this.data[prev][c] = 0;
          					this.score += this.data[r][c];
          				}
          			}else{
          				break;
          			}
          		}
          	},
          	getPrevIncol: function(r,c){
          		for(var i = r - 1; i >= 0; i--){
          			if(this.data[i][c] != 0){
          				return i;
          			}
          		}
          		return -1;
          	},
          }
          game.start();
          console.log(game.data)
          console.log(game.status);
          console.log(game.score);
          //鍵盤事件
          document.onkeydown = function(){
          	if(event.keyCode == 37){
          		//console.log("左")
          		game.moveLeft();
          	}else if(event.keyCode == 38){
          		//console.log("上")
          		game.moveUp()
          	}else if(event.keyCode == 39){
          		//console.log("右")
          		game.moveRight()
          	}else if(event.keyCode == 40){
          		//console.log("下")
          		game.moveDown()
          	}
          }
          //touch事件
          //手指按下
          var startX;//設(shè)定開始起始位置的x坐標(biāo)
          var startY;//設(shè)定開始起始位置的y坐標(biāo)
          var endX;//設(shè)定結(jié)束滑動位置的x坐標(biāo)
          var endY;//設(shè)定結(jié)束滑動位置的y坐標(biāo)
          document.addEventListener('touchstart',function(){
          //	console.log("手指按下了屏幕")
          	console.log(event);
          	startX = event.touches[0].pageX;
          	startY = event.touches[0].pageY;
          })
          //手指移動
          //document.addEventListener('touchmove',function(){
          //	console.log("手指的移動")
          //})
          //手指松開
          document.addEventListener("touchend",function(){
          //	console.log("手指松開")
          	console.log(event);
          	endX = event.changedTouches[0].pageX;//如何獲取結(jié)束時的位置x
          	endY = event.changedTouches[0].pageY;
          	var X = endX - startX;
          	var Y = endY - startY
          	var absX = Math.abs(X) > Math.abs(Y);
          	var absY = Math.abs(Y) > Math.abs(X);
          	if(X > 0 && absX){
          		console.log("右滑動")
          		game.moveRight()
          	}else if(X < 0 && absX){
          		console.log("左滑動")
          		game.moveLeft()
          	}if(Y > 0 && absY){
          		console.log("下滑動")
          		game.moveDown()
          	}if(Y < 0 && absY){
          		console.log("上滑動")
          		game.moveUp()
          	}
          })

          如下為游戲效果圖

          就這樣一個簡單的2048游戲就完成啦~


          最后

          非常感謝您能看到這里~

          關(guān)注我~帶給你更多驚喜~

          、背景



          一方面想對git底層工作原理有更多理解,另外觀摩下Linus大神的編碼思路和風(fēng)格

          二、版本選擇

          git項(xiàng)目地址:https://github.com/git/git.git

          開源庫隨著功能增加代碼越來越龐大,并且主干思想也會被越來越多的分支細(xì)節(jié)所淹沒,所以直接選擇git第一個版本代碼進(jìn)行參考。時間回到2005年4月7號的下午。

          commit e83c5163316f89bfbde7d9ab23ca2e25604af290 (HEAD)
          Author: Linus Torvalds <torvalds@ppc970.osdl.org>
          Date:   Thu Apr 7 15:13:13 2005 -0700

          進(jìn)到目錄里看一下,只有幾個文件,總共代碼行才1000出頭

          三、代碼運(yùn)行

          review+debug是學(xué)習(xí)代碼庫非常有效的方法,所以先讓代碼跑起來。mac上嘗試編譯,出現(xiàn)一些警告以及錯誤。本地做一些修改后編譯通過。有同學(xué)要自己嘗試動手,可以參照以下修改:

          1、安裝openssl庫以及zlib庫

          brew install openssl

          brew install zlib

          2、修改編譯以及鏈接選項(xiàng)并指定頭文件以及庫位置同時關(guān)閉棄用函數(shù)報警

          CFLAGS=-g -Wno-deprecated -I/usr/local/opt/openssl/include/

          LDFLAGS=-L/usr/local/opt/openssl/lib/ -L/usr/local/opt/zlib/lib/

          3、鏈接庫修改,從 -lssl 改為 -lcrypto -lz

          4、main函數(shù)增加返回值、修改時間相關(guān)結(jié)構(gòu)體

          5、m1芯片mac沒有找到可用gdb版本,可以使用lldb代替

          以下是具體修改點(diǎn):

          [graypig:]$ git diff
          diff --git a/Makefile b/Makefile
          index a6bba79ba1..fe779bdb75 100644
          --- a/Makefile
          +++ b/Makefile
          @@ -1,4 +1,5 @@
          -CFLAGS=-g
          +CFLAGS=-g -Wno-deprecated -I/usr/local/opt/openssl/include/
          +LDFLAGS=-L/usr/local/opt/openssl/lib/ -L/usr/local/opt/zlib/lib/
           CC=gcc
           
           PROG=update-cache show-diff init-db write-tree read-tree commit-tree cat-file
          @@ -8,27 +9,27 @@ all: $(PROG)
           install: $(PROG)
                  install $(PROG) $(HOME)/bin/
           
          -LIBS= -lssl
          +LIBS= -lcrypto -lz
           
           init-db: init-db.o
           
           update-cache: update-cache.o read-cache.o
          -       $(CC) $(CFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
          +       $(CC) $(CFLAGS) $(LDFLAGS) -o update-cache update-cache.o read-cache.o $(LIBS)
          
           show-diff: show-diff.o read-cache.o
          -       $(CC) $(CFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
          +       $(CC) $(CFLAGS) $(LDFLAGS) -o show-diff show-diff.o read-cache.o $(LIBS)
           
           write-tree: write-tree.o read-cache.o
          -       $(CC) $(CFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
          +       $(CC) $(CFLAGS) $(LDFLAGS) -o write-tree write-tree.o read-cache.o $(LIBS)
           
           read-tree: read-tree.o read-cache.o
          -       $(CC) $(CFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
          +       $(CC) $(CFLAGS) $(LDFLAGS) -o read-tree read-tree.o read-cache.o $(LIBS)
           
           commit-tree: commit-tree.o read-cache.o
          -       $(CC) $(CFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
          +       $(CC) $(CFLAGS) $(LDFLAGS) -o commit-tree commit-tree.o read-cache.o $(LIBS)
           
           cat-file: cat-file.o read-cache.o
          -       $(CC) $(CFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
          +       $(CC) $(CFLAGS) $(LDFLAGS) -o cat-file cat-file.o read-cache.o $(LIBS)
           
           read-cache.o: cache.h
           show-diff.o: cache.h
          diff --git a/cache.h b/cache.h
          index 98a32a9ad3..161a5aff90 100644
          --- a/cache.h
          +++ b/cache.h
          @@ -12,6 +12,7 @@
           
           #include <openssl/sha.h>
           #include <zlib.h>
          +#include <string.h>
           
           /*
            * Basic data structures for the directory cache
          diff --git a/init-db.c b/init-db.c
          index 25dc13fe10..d11b16bff5 100644
          --- a/init-db.c
          +++ b/init-db.c
          @@ -19,8 +19,8 @@ int main(int argc, char **argv)
                  sha1_dir = getenv(DB_ENVIRONMENT);
                  if (sha1_dir) {
                          struct stat st;
          -               if (!stat(sha1_dir, &st) < 0 && S_ISDIR(st.st_mode))
          -                       return;
          +               if (!(stat(sha1_dir, &st) < 0) && S_ISDIR(st.st_mode))
          +                       return 0;
                          fprintf(stderr, "DB_ENVIRONMENT set to bad directory %s: ", sha1_dir);
                  }
           
          diff --git a/show-diff.c b/show-diff.c
          index b8522886a1..6d00ba2a6f 100644
          --- a/show-diff.c
          +++ b/show-diff.c
          @@ -11,11 +11,11 @@ static int match_stat(struct cache_entry *ce, struct stat *st)
           {
                  unsigned int changed = 0;
           
          -       if (ce->mtime.sec  != (unsigned int)st->st_mtim.tv_sec ||
          -           ce->mtime.nsec != (unsigned int)st->st_mtim.tv_nsec)
          +       if (ce->mtime.sec  != (unsigned int)st->st_mtimespec.tv_sec ||
          +           ce->mtime.nsec != (unsigned int)st->st_mtimespec.tv_nsec)
                          changed |= MTIME_CHANGED;
          -       if (ce->ctime.sec  != (unsigned int)st->st_ctim.tv_sec ||
          -           ce->ctime.nsec != (unsigned int)st->st_ctim.tv_nsec)
          +       if (ce->ctime.sec  != (unsigned int)st->st_ctimespec.tv_sec ||
          +           ce->ctime.nsec != (unsigned int)st->st_ctimespec.tv_nsec)
                          changed |= CTIME_CHANGED;
                  if (ce->st_uid != (unsigned int)st->st_uid ||
                      ce->st_gid != (unsigned int)st->st_gid)
          diff --git a/update-cache.c b/update-cache.c
          index 5085a5cb53..f9c8e0fc69 100644
          --- a/update-cache.c
          +++ b/update-cache.c
          @@ -139,9 +139,9 @@ static int add_file_to_cache(char *path)
                  memset(ce, 0, size);
                  memcpy(ce->name, path, namelen);
                  ce->ctime.sec = st.st_ctime;
          -       ce->ctime.nsec = st.st_ctim.tv_nsec;
          +       ce->ctime.nsec = st.st_ctimespec.tv_nsec;
                  ce->mtime.sec = st.st_mtime;
          -       ce->mtime.nsec = st.st_mtim.tv_nsec;
          +       ce->mtime.nsec = st.st_mtimespec.tv_nsec;
                  ce->st_dev = st.st_dev;
                  ce->st_ino = st.st_ino;
                  ce->st_mode = st.st_mode;

          四、源碼分析

          1、 init-db.c

          核心邏輯:創(chuàng)建緩存目錄.dircache/objects,并且在此目錄下預(yù)創(chuàng)建256個目錄,命名規(guī)則

          .dircache/objects/00 .dircache/objects/01 .dircache/objects/... .dircache/objects/ff

          #include "cache.h"
          
          int main(int argc, char **argv)
          {
              char *sha1_dir = getenv(DB_ENVIRONMENT), *path;
              int len, i, fd;
          
              if (mkdir(".dircache", 0700) < 0) {
                  perror("unable to create .dircache");
                  exit(1);
              }
          
              /*
               * If you want to, you can share the DB area with any number of branches.
               * That has advantages: you can save space by sharing all the SHA1 objects.
               * On the other hand, it might just make lookup slower and messier. You
               * be the judge.
               */
              sha1_dir = getenv(DB_ENVIRONMENT);
              if (sha1_dir) {
                  struct stat st;
                  if (!stat(sha1_dir, &st) < 0 && S_ISDIR(st.st_mode))
                      return;
                  fprintf(stderr, "DB_ENVIRONMENT set to bad directory %s: ", sha1_dir);
              }
          
              /*
               * The default case is to have a DB per managed directory. 
               */
              sha1_dir = DEFAULT_DB_ENVIRONMENT;
              fprintf(stderr, "defaulting to private storage area\n");
              len = strlen(sha1_dir);
              if (mkdir(sha1_dir, 0700) < 0) {
                  if (errno != EEXIST) {
                      perror(sha1_dir);
                      exit(1);
                  }
              }
              //注意malloc申請內(nèi)存后不會清零,但是使用sprintf格式化會在末尾添加\0,所以不存在越界問題
              path = malloc(len + 40);
              memcpy(path, sha1_dir, len);
              for (i = 0; i < 256; i++) {
                  //兩個16進(jìn)制字符格式打印
                  sprintf(path+len, "/%02x", i);
                  if (mkdir(path, 0700) < 0) {
                      if (errno != EEXIST) {
                          perror(path);
                          exit(1);
                     }
                  }
              }
              return 0;
          }

          2、update-cache.c

          緩存項(xiàng)設(shè)計(jì)經(jīng)過仔細(xì)考量,可以直接利用文件字節(jié)流還原內(nèi)存緩存項(xiàng)結(jié)構(gòu),省掉了拷貝動作。核心邏輯:

          首先讀取.dircache/index的文件內(nèi)容,對要加入緩存的文件進(jìn)行校驗(yàn)后,進(jìn)行zlib壓縮并計(jì)算sha1值,按照sha1計(jì)算文件的路徑.dircache/objects/xx/xx{19}, 保存文件然后更新全局cache信息,并將全局cache保存到磁盤上生成新的.dircache/index。

          文件內(nèi)容索引文件格式:"blob " + size + null + zlib壓縮后的文件內(nèi)容

          int main(int argc, char **argv)
          {
              int i, newfd, entries;
          
              entries = read_cache();
              if (entries < 0) {
                  perror("cache corrupted");
                  return -1;
              }
          
              newfd = open(".dircache/index.lock", O_RDWR | O_CREAT | O_EXCL, 0600);
              if (newfd < 0) {
                  perror("unable to create new cachefile");
                  return -1;
              }
              for (i = 1 ; i < argc; i++) {
                  char *path = argv[i];
                  // 判斷路徑是否合法,排除: . .. //結(jié)尾
                  if (!verify_path(path)) {
                      fprintf(stderr, "Ignoring path %s\n", argv[i]);
                        continue;
                  }
                  if (add_file_to_cache(path)) {
                      fprintf(stderr, "Unable to add %s to database\n", path);
                      goto out;
                  }
              }
              if (!write_cache(newfd, active_cache, active_nr) && !rename(".dircache/index.lock", ".dircache/index"))
                  return 0;
          out:
              unlink(".dircache/index.lock");
          }

          2.1 緩存讀取邏輯

          read_cache讀取緩存邏輯:打開緩存文件.dircache/index,通過mmap將文件映射到內(nèi)存,校驗(yàn)文件sha1,根據(jù)頭enty個數(shù)還原緩存數(shù)據(jù)。

             int read_cache(void)
          {
              int fd, i;
              struct stat st;
              unsigned long size, offset;
              void *map;
              struct cache_header *hdr;
          
              errno = EBUSY;
              if (active_cache)
                  return error("more than one cachefile");
              errno = ENOENT;
              sha1_file_directory = getenv(DB_ENVIRONMENT);
              if (!sha1_file_directory)
                  sha1_file_directory = DEFAULT_DB_ENVIRONMENT;
              if (access(sha1_file_directory, X_OK) < 0)
                  return error("no access to SHA1 file directory");
              fd = open(".dircache/index", O_RDONLY);
              if (fd < 0)
                  return (errno == ENOENT) ? 0 : error("open failed");
          
              map = (void *)-1;
              if (!fstat(fd, &st)) {
                  map = NULL;
                  size = st.st_size;
                  errno = EINVAL;
                  if (size > sizeof(struct cache_header))
                      map = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
              }
              close(fd);
              if (-1 == (int)(long)map)
                  return error("mmap failed");
          
              hdr = map;
              if (verify_hdr(hdr, size) < 0)
                  goto unmap;
          
              // 根據(jù)緩存數(shù)量來申請內(nèi)存,預(yù)留1.5倍空間
              active_nr = hdr->entries;
              active_alloc = alloc_nr(active_nr);
              active_cache = calloc(active_alloc, sizeof(struct cache_entry *));
              // 通過文件字節(jié)直接還原內(nèi)存結(jié)構(gòu)
              offset = sizeof(*hdr);
              for (i = 0; i < hdr->entries; i++) {
                  struct cache_entry *ce = map + offset;
                  offset = offset + ce_size(ce);
                  active_cache[i] = ce;
              }
              return active_nr;
          
          unmap:
              munmap(map, size);
              errno = EINVAL;
              return error("verify header failed");
          }

          verify_hdr校驗(yàn)緩存頭:通過緩存重新計(jì)算sha1,跟緩存頭sha1對比進(jìn)行校驗(yàn)

          static int verify_hdr(struct cache_header *hdr, unsigned long size)
          {
             SHA_CTX c;
             unsigned char sha1[20];
             // 基礎(chǔ)校驗(yàn), 簽名&版本
             if (hdr->signature != CACHE_SIGNATURE)
                return error("bad signature");
             if (hdr->version != 1)
                return error("bad version");
             SHA1_Init(&c);
             // 提取緩存頭中除了sha1部分的數(shù)據(jù)
             SHA1_Update(&c, hdr, offsetof(struct cache_header, sha1));
             // 提取緩存內(nèi)容數(shù)據(jù),hdr+1是指跳過緩存頭
             SHA1_Update(&c, hdr+1, size - sizeof(*hdr));
             // 計(jì)算sha1
             SHA1_Final(sha1, &c);
             // 對比sha1
             if (memcmp(sha1, hdr->sha1, 20))
                return error("bad header sha1");
             return 0;
          }

          特殊宏函數(shù)說明:

          struct cache_entry {
             struct cache_time ctime;
             struct cache_time mtime;
             unsigned int st_dev;
             unsigned int st_ino;
             unsigned int st_mode;
             unsigned int st_uid;
             unsigned int st_gid;
             unsigned int st_size;
             unsigned char sha1[20];
             unsigned short namelen;
             // 0長度字符數(shù)組,并不占用空間
             unsigned char name[0];
          };
          
          // 計(jì)算緩存項(xiàng)的長度
          #define ce_size(ce) cache_entry_size((ce)->namelen)
          
          /*
           offsetof(struct cache_entry,name)獲取name在結(jié)構(gòu)體中的偏移,即除去name之外的緩存項(xiàng)目大小
            & ~7 將最低3位置0,也就是說將最終的長度對8對齊
            +8  為了防止將最低3位置0后大小變小,因此提前+8來預(yù)留空間
            
           */
          #define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)

          2.2 文件加入緩存邏輯

          獲取文件meta信息,給文件建立索引,將文件加入緩存entry.

          
          static int add_file_to_cache(char *path)
          {
             int size, namelen;
             struct cache_entry *ce;
             struct stat st;
             int fd;
          
             fd = open(path, O_RDONLY);
             if (fd < 0) {
                if (errno == ENOENT)
                   return remove_file_from_cache(path);
                return -1;
             }
             if (fstat(fd, &st) < 0) {
                close(fd);
                return -1;
             }
             namelen = strlen(path);
             size = cache_entry_size(namelen);
             ce = malloc(size);
             memset(ce, 0, size);
             memcpy(ce->name, path, namelen);
             ce->ctime.sec = st.st_ctime;
             ce->ctime.nsec = st.st_ctimespec.tv_nsec;
             ce->mtime.sec = st.st_mtime;
             ce->mtime.nsec = st.st_mtimespec.tv_nsec;
             ce->st_dev = st.st_dev;
             ce->st_ino = st.st_ino;
             ce->st_mode = st.st_mode;
             ce->st_uid = st.st_uid;
             ce->st_gid = st.st_gid;
             ce->st_size = st.st_size;
             ce->namelen = namelen;
          
             if (index_fd(path, namelen, ce, fd, &st) < 0)
                return -1;
          
             return add_cache_entry(ce);
          }

          文件建索引流程:將文件mmap到內(nèi)存,使用zlib壓縮meta信息(blob size + null byte), 壓縮文件內(nèi)容,計(jì)算sha1,根據(jù)sha1計(jì)算緩存文件名,寫入緩存文件。

          
          static int index_fd(const char *path, int namelen, struct cache_entry *ce, int fd, struct stat *st)
          {
             z_stream stream;
             int max_out_bytes = namelen + st->st_size + 200;
             void *out = malloc(max_out_bytes);
             void *metadata = malloc(namelen + 200);
             void *in = mmap(NULL, st->st_size, PROT_READ, MAP_PRIVATE, fd, 0);
             SHA_CTX c;
          
             close(fd);
             if (!out || (int)(long)in == -1)
                return -1;
          
             memset(&stream, 0, sizeof(stream));
             deflateInit(&stream, Z_BEST_COMPRESSION);
          
             // 壓縮meta信息
             /*
              * ASCII size + nul byte
              */    
             stream.next_in = metadata;
             stream.avail_in = 1+sprintf(metadata, "blob %lu", (unsigned long) st->st_size);
             stream.next_out = out;
             stream.avail_out = max_out_bytes;
             while (deflate(&stream, 0) == Z_OK)
                /* nothing */;
          
             /*
              * File content
              */
             // 壓縮文件內(nèi)容
             stream.next_in = in;
             stream.avail_in = st->st_size;
             while (deflate(&stream, Z_FINISH) == Z_OK)
                /*nothing */;
          
             deflateEnd(&stream);
             
             SHA1_Init(&c);
             SHA1_Update(&c, out, stream.total_out);
             // 計(jì)算sha1
             SHA1_Final(ce->sha1, &c);
          
             // 文件內(nèi)容寫入緩存
             return write_sha1_buffer(ce->sha1, out, stream.total_out);
          }
          int write_sha1_buffer(unsigned char *sha1, void *buf, unsigned int size)
          {
             char *filename = sha1_file_name(sha1);
             int i, fd;
          
             fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);
             if (fd < 0)
                return (errno == EEXIST) ? 0 : -1;
             write(fd, buf, size);
             close(fd);
             return 0;
          }

          根據(jù)哈希值計(jì)算文件名: 第一個哈希值決定目錄,剩余的19個哈希值決定文件名

          /*
           * NOTE! This returns a statically allocated buffer, so you have to be
           * careful about using it. Do a "strdup()" if you need to save the
           * filename.
           */
          char *sha1_file_name(unsigned char *sha1)
          {
             int i;
             static char *name, *base;
          
             if (!base) {
                char *sha1_file_directory = getenv(DB_ENVIRONMENT) ? : DEFAULT_DB_ENVIRONMENT;
                int len = strlen(sha1_file_directory);
                base = malloc(len + 60);
                memcpy(base, sha1_file_directory, len);
                memset(base+len, 0, 60);
                // .dircache/objects/xx/xx{19}
                base[len] = '/';
                base[len+3] = '/';
                name = base + len + 1;
             }
             for (i = 0; i < 20; i++) {
                static char hex[] = "0123456789abcdef";
                unsigned int val = sha1[i];
                //根據(jù)哈希值計(jì)算文件名。第一個哈希值決定目錄,剩余的19個哈希值決定文件名
                // i > 0是用來跳過"/", 第一個哈希值在"/"前,剩余的19個哈希值在"/"后
                char *pos = name + i*2 + (i > 0);
                *pos++ = hex[val >> 4];
                *pos = hex[val & 0xf];
             }
             return base;
          }

          add_cache_entry將文件加入緩存: 緩存按照文件路徑排序,二分查找。

          
          static int add_cache_entry(struct cache_entry *ce)
          {
             int pos;
          
             pos = cache_name_pos(ce->name, ce->namelen);
          
             /* existing match? Just replace it */
             if (pos < 0) {
                active_cache[-pos-1] = ce;
                return 0;
             }
          
             /* Make sure the array is big enough .. */
             if (active_nr == active_alloc) {
                active_alloc = alloc_nr(active_alloc);
                active_cache = realloc(active_cache, active_alloc * sizeof(struct cache_entry *));
             }
          
             /* Add it in.. */
             active_nr++;
             // 要插入的位置不在最后,從pos開始元素向后移動
             if (active_nr > pos)
                memmove(active_cache + pos + 1, active_cache + pos, (active_nr - pos - 1) * sizeof(ce));
             active_cache[pos] = ce;
             return 0;
          }

          cache_name_pos根據(jù)名字獲取緩存項(xiàng)的位置,二分查找。 這個函數(shù)返回值比較特殊, 沒有找到返回最后一次查找first(>0),找到了返回 -p -1(<0) 。這樣設(shè)計(jì),基于性能考慮,找到時返回了位置,在沒有找到的時候返回了要插入的位置。

           static int cache_name_pos(const char *name, int namelen)
          {
             int first, last;
          
             first = 0;
             last = active_nr;
             while (last > first) {
                int next = (last + first) >> 1;
                struct cache_entry *ce = active_cache[next];
                int cmp = cache_name_compare(name, namelen, ce->name, ce->namelen);
                if (!cmp)
                   return -next-1;
                if (cmp < 0) {
                   last = next;
                   continue;
                }
                first = next+1;
             }
             return first;
          }

          cache_name_compare,先比較名稱,再比較長度, 0相等,-1 小于,1 大于

          static int cache_name_compare(const char *name1, int len1, const char *name2, int len2)
          {
             int len = len1 < len2 ? len1 : len2;
             int cmp;
          
             cmp = memcmp(name1, name2, len);
             if (cmp)
                return cmp;
             if (len1 < len2)
                return -1;
             if (len1 > len2)
                return 1;
             return 0;
          }

          3、show-diff.c

          核心邏輯:首先讀取緩存,針對緩存中的每個entry,根據(jù)meta判斷文件當(dāng)前是否有變更,如果有打印文件路徑以及sha1,并且根據(jù)sha1找到文件并解壓文件內(nèi)容,并調(diào)用系統(tǒng)的diff(diff -u - ${name})命令打印差異。 對diff命令,-代表標(biāo)準(zhǔn)輸入

          
          int main(int argc, char **argv)
          {
             int entries = read_cache();
             int i;
          
             if (entries < 0) {
                perror("read_cache");
                exit(1);
             }
             for (i = 0; i < entries; i++) {
                struct stat st;
                struct cache_entry *ce = active_cache[i];
                int n, changed;
                unsigned int mode;
                unsigned long size;
                char type[20];
                void *new;
          
                if (stat(ce->name, &st) < 0) {
                   printf("%s: %s\n", ce->name, strerror(errno));
                   continue;
                }
                changed = match_stat(ce, &st);
                if (!changed) {
                   printf("%s: ok\n", ce->name);
                   continue;
                }
                printf("%.*s:  ", ce->namelen, ce->name);
                for (n = 0; n < 20; n++)
                   printf("%02x", ce->sha1[n]);
                printf("\n");
                new = read_sha1_file(ce->sha1, type, &size);
                show_differences(ce, &st, new, size);
                free(new);
             }
             return 0;
          }

          show_differences 執(zhí)行系統(tǒng)命令diff打印差異

          static void show_differences(struct cache_entry *ce, struct stat *cur,
             void *old_contents, unsigned long long old_size)
          {
             static char cmd[1000];
             FILE *f;
          
             snprintf(cmd, sizeof(cmd), "diff -u - %s", ce->name);
             f = popen(cmd, "w");
             fwrite(old_contents, old_size, 1, f);
             pclose(f);
          }

          4、cat-file.c

          核心邏輯:按照sha1計(jì)算緩存文件名,讀取文件解壓將內(nèi)容寫入臨時文件,并且打印類型以及長度

          int main(int argc, char **argv)
          {
             unsigned char sha1[20];
             char type[20];
             void *buf;
             unsigned long size;
             char template[] = "temp_git_file_XXXXXX";
             int fd;
          
             if (argc != 2 || get_sha1_hex(argv[1], sha1))
                usage("cat-file: cat-file <sha1>");
             buf = read_sha1_file(sha1, type, &size);
             if (!buf)
                exit(1);
             fd = mkstemp(template);
             if (fd < 0)
                usage("unable to create tempfile");
             if (write(fd, buf, size) != size)
                strcpy(type, "bad");
             printf("%s: %s\n", template, type);
          }


          5、write-tree.c

          核心邏輯:讀取文件緩存數(shù)據(jù),組成樹內(nèi)容。內(nèi)容格式:tree size null mode name null [mode name null] mode name null。然后根據(jù)文件內(nèi)容計(jì)算sha1,根據(jù)sha1計(jì)算文件路徑,將壓縮后的數(shù)據(jù)寫入文件

          
          int main(int argc, char **argv)
          {
             unsigned long size, offset, val;
             int i, entries = read_cache();
             char *buffer;
          
             if (entries <= 0) {
                fprintf(stderr, "No file-cache to create a tree of\n");
                exit(1);
             }
          
             /* Guess at an initial size */
             size = entries * 40 + 400;
             buffer = malloc(size);
             offset = ORIG_OFFSET;
          
             for (i = 0; i < entries; i++) {
                struct cache_entry *ce = active_cache[i];
                if (check_valid_sha1(ce->sha1) < 0)
                   exit(1);
                // 空間不夠重新申請
                if (offset + ce->namelen + 60 > size) {
                   size = alloc_nr(offset + ce->namelen + 60);
                   buffer = realloc(buffer, size);
                }
                // 格式:十進(jìn)制權(quán)限 文件名 NULL sha1
                offset += sprintf(buffer + offset, "%o %s", ce->st_mode, ce->name);
                buffer[offset++] = 0;
                memcpy(buffer + offset, ce->sha1, 20);
                offset += 20;
             }
             /*
                offset - ORIG_OFFSET 數(shù)據(jù)長度
                ORIG_OFFSET 數(shù)據(jù)偏移
                將數(shù)據(jù)長度寫到預(yù)留空間的尾部,向前填入"tree ",并調(diào)整buffer offset位置
                整體數(shù)據(jù)格式: tree size null mode name null sha1 [mode name null sha1] ... mode name null sha1
             */
             i = prepend_integer(buffer, offset - ORIG_OFFSET, ORIG_OFFSET);
             i -= 5;
             memcpy(buffer+i, "tree ", 5);
          
             buffer += i;
             offset -= i;
          
             write_sha1_file(buffer, offset);
             return 0;
          }

          prepend_integer 從i個位置向前以字符串形式填寫val,并返回新的i

          static int prepend_integer(char *buffer, unsigned val, int i)
          {
             buffer[--i] = '\0';
             do {
                buffer[--i] = '0' + (val % 10);
                val /= 10;
             } while (val);
             return i;
          }

          數(shù)據(jù)樣例

          x buf x/60b buf

          https://wenku.baidu.com/view/62a4aea6e63a580216fc700abb68a98271feacb0.html?_wkts_=1676432746759&bdQuery=lldb+%E8%BF%9E%E7%BB%AD%E5%86%85%E5%AD%98


          6、commit-tree.c

          基礎(chǔ)邏輯:校驗(yàn)參數(shù)后,獲取當(dāng)前登錄用戶的密碼相關(guān)信息,用來獲取用戶名、email,記錄changgelog。記錄當(dāng)前commit sha1,parent sha1 、author 、committer以及 評論信息,調(diào)整緩存頭"commit size." 根據(jù)文件內(nèi)容sha1計(jì)算文件名,并保存到object目錄。

          
          int main(int argc, char **argv)
          {
             int i, len;
             int parents = 0;
             unsigned char tree_sha1[20];
             unsigned char parent_sha1[MAXPARENT][20];
             char *gecos, *realgecos;
             char *email, realemail[1000];
             char *date, *realdate;
             char comment[1000];
             struct passwd *pw;
             time_t now;
             char *buffer;
             unsigned int size;
          
             if (argc < 2 || get_sha1_hex(argv[1], tree_sha1) < 0)
                usage("commit-tree <sha1> [-p <sha1>]* < changelog");
          
             for (i = 2; i < argc; i += 2) {
                char *a, *b;
                a = argv[i]; b = argv[i+1];
                if (!b || strcmp(a, "-p") || get_sha1_hex(b, parent_sha1[parents]))
                   usage("commit-tree <sha1> [-p <sha1>]* < changelog");
                parents++;
             }
             if (!parents)
                fprintf(stderr, "Committing initial tree %s\n", argv[1]);
             // 讀取當(dāng)前用戶密碼信息,用來記錄changelog
             pw = getpwuid(getuid());
             if (!pw)
                usage("You don't exist. Go away!");
             realgecos = pw->pw_gecos;
             len = strlen(pw->pw_name);
             memcpy(realemail, pw->pw_name, len);
             realemail[len] = '@';
             gethostname(realemail+len+1, sizeof(realemail)-len-1);
             time(&now);
             realdate = ctime(&now);
          
             gecos = getenv("COMMITTER_NAME") ? : realgecos;
             email = getenv("COMMITTER_EMAIL") ? : realemail;
             date = getenv("COMMITTER_DATE") ? : realdate;
          
             remove_special(gecos); remove_special(realgecos);
             remove_special(email); remove_special(realemail);
             remove_special(date); remove_special(realdate);
          
             init_buffer(&buffer, &size);
             add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
          
             /*
              * NOTE! This ordering means that the same exact tree merged with a
              * different order of parents will be a _different_ changeset even
              * if everything else stays the same.
              */
             for (i = 0; i < parents; i++)
                add_buffer(&buffer, &size, "parent %s\n", sha1_to_hex(parent_sha1[i]));
          
             /* Person/date information */
             add_buffer(&buffer, &size, "author %s <%s> %s\n", gecos, email, date);
             add_buffer(&buffer, &size, "committer %s <%s> %s\n\n", realgecos, realemail, realdate);
          
             /* And add the comment */
             while (fgets(comment, sizeof(comment), stdin) != NULL)
                add_buffer(&buffer, &size, "%s", comment);
          
             finish_buffer("commit ", &buffer, &size);
          
             write_sha1_file(buffer, size);
             return 0;
          } 

          緩存處理邏輯:初始化了16K基本緩存大小,預(yù)留了40字節(jié)頭信息,每32k realloc一次內(nèi)存。代碼存在BUG,應(yīng)該是筆誤,16k 32k應(yīng)該設(shè)置成一樣大小,否則特殊場景會崩。

          
          #define BLOCKING (1ul << 14)
          #define ORIG_OFFSET (40)
          
          /*
           * Leave space at the beginning to insert the tag
           * once we know how big things are.
           *
           * FIXME! Share the code with "write-tree.c"
           */
          static void init_buffer(char **bufp, unsigned int *sizep)
          {
             char *buf = malloc(BLOCKING);
             memset(buf, 0, ORIG_OFFSET);
             *sizep = ORIG_OFFSET;
             *bufp = buf;
          }
          
          static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
          {
             char one_line[2048];
             va_list args;
             int len;
             unsigned long alloc, size, newsize;
             char *buf;
          
             va_start(args, fmt);
             len = vsnprintf(one_line, sizeof(one_line), fmt, args);
             va_end(args);
             size = *sizep;
             newsize = size + len;
             alloc = (size + 32767) & ~32767;
             buf = *bufp;
             if (newsize > alloc) {
                alloc = (newsize + 32767) & ~32767;   
                buf = realloc(buf, alloc);
                *bufp = buf;
             }
             *sizep = newsize;
             memcpy(buf + size, one_line, len);
          }


          五、總結(jié)

          設(shè)計(jì)巧妙,代碼簡潔工整,注重性能,注釋自由

          1、基礎(chǔ)模型

          git里兩個基本概念:The Object Database、Current Directory Cache

          The Object Database

          對象數(shù)據(jù)庫,對象內(nèi)容采用zlib壓縮,對象名采用sha1,包含三類對象,BLOB(普通文件內(nèi)容)、TREE(文件權(quán)限/名稱/sha1集合,表示一次提交的內(nèi)容)、

          CHANGESET(TREE父子鏈,表示變更歷史)。

          Current Directory Cache

          git暫存區(qū),當(dāng)前緩存的文件的META信息

          2、功能維度

          git第一版代碼保持了linux工具鏈風(fēng)格,每個工具只干一件事情,底層工具組合在一起完成代碼管理功能

          1)update-cache:git add雛形,保存最新文件內(nèi)容到objects里,并更新本地目錄緩存

          2)show-diff:git status雛形,實(shí)現(xiàn)了緩存中的文件與最新狀態(tài)差異對比

          3)write-tree: git commit雛形1,保存工作區(qū)最新緩存樹到objects目錄并生成sha1

          4)commit-tree: git commit雛形2,保存提交的樹的sha1以及的parent樹的sha1到object目錄并生成sha1

          理解了這些工具實(shí)現(xiàn)邏輯,不難想象目前git的各種命令和概念的原理。比如分支,分支本質(zhì)只是一個changeset的sha1,基于sha1可以反向追溯每一次提交的tree。要實(shí)現(xiàn)兩次提交diff,對比兩個tree可以找到目錄差異以及變化的文件,基于文件的sha1可以找到文件進(jìn)而對比出文件的變化。分支拷貝,底層操作只需要拷貝一個sha1值,等等。

          3、性能維度

          實(shí)現(xiàn)功能同時充分考量性能,緩存項(xiàng)頭格式設(shè)計(jì)、二分查找返回值的設(shè)計(jì)、文件內(nèi)容頭信息、文件訪問采用mmap避免內(nèi)核緩沖區(qū)到用戶緩沖區(qū)數(shù)據(jù)拷貝

          雖然對常規(guī)業(yè)務(wù)來講,可讀性高于性能,但隨手可得的優(yōu)化是程序員基本素養(yǎng)


          主站蜘蛛池模板: 国产一区二区三区乱码| 在线成人一区二区| 国产成人一区二区三区免费视频 | 国产AV一区二区三区传媒| 日本在线视频一区| 精品人妻码一区二区三区| 国产精品自拍一区| 久久久人妻精品无码一区| 91国偷自产一区二区三区| 欧洲精品免费一区二区三区| 一区二区三区内射美女毛片| 一区二区三区免费在线视频 | 国产av夜夜欢一区二区三区| 日韩AV无码一区二区三区不卡毛片| 国产综合一区二区| 亚洲一区二区三区深夜天堂| 久久影院亚洲一区| 亚洲国产AV一区二区三区四区 | 一区二区精品视频| 亲子乱av一区区三区40岁| 国产AV午夜精品一区二区三区| 国产一区二区三区免费在线观看| 少妇无码一区二区三区免费| 日韩在线视频一区二区三区 | 日韩在线一区二区| 精品爆乳一区二区三区无码av| 日美欧韩一区二去三区| 国产精品日本一区二区不卡视频| 日韩精品无码中文字幕一区二区| 国产亚洲日韩一区二区三区| 无码一区二区波多野结衣播放搜索 | 国产日本亚洲一区二区三区| 亲子乱AV视频一区二区| 少妇人妻偷人精品一区二区| 亚洲AV成人一区二区三区AV| 日本一区二区在线免费观看| AV无码精品一区二区三区| 日韩免费无码一区二区视频| 日本精品视频一区二区三区| 国产日产久久高清欧美一区| 日韩精品一区二区三区不卡|