于 丙超 2012 年 6 月 28 日發(fā)布 目標要做一個完整的雙人對弈游戲,至少要做如下事情,第一步:繪制棋盤。不同的棋類游戲棋盤不同,這一點需要進行動態(tài)處理;第二步:繪制棋子。需要說明的是,圍棋,五子棋等這些棋子都是圓的啊,請不要為了圖片苦惱,在 HTML5 時代,我們用代碼就可以實現(xiàn)立體圓形棋子;第三步:判斷落子事件。當然是要定位手指的點擊位置,這四種棋中,有的是落在框里面的,有的卻是落在縱橫交錯的棋盤十字線上,需要動態(tài)處理;第四步:判斷落子規(guī)則。下棋都有規(guī)則,不要因為代碼少,就將規(guī)則打折扣,否則程序不成熟,會變成小朋友的玩具了;第五步:判斷輸贏。最后,我們要判斷輸贏。也就是要數(shù)子,這個事情必須由程序來完成,因為下棋總得需要一個裁判嘛;第六步:就是平板電腦時代,我們得實現(xiàn)離線應用。這個太重要了,否則,要是在臺式電腦上,接根網(wǎng)線玩的游戲,已經(jīng)遍地都是了,您寫得再牛,有什么用?就是要移動,在沒有信號的地方,才有市場,現(xiàn)在平板,智能手機這么多,在沒有網(wǎng)絡信號的地方,掏出移動設備來下棋,才是一件很牛的事情。 繪制棋盤前面說了圍棋、五子棋、四子棋和翻轉(zhuǎn)棋的棋盤并不相同,圍棋是縱橫 18 個格,其他三種棋則是 8 個格。所以繪制棋盤是需要有參數(shù)。這是個小問題,大問題是,選擇什么方法來繪制棋盤? HTML5 框架下,有至少 3 種方法:第一種,用 Canvas 畫線;第二種,用 DIV,CSS3 里面增加了行列屬性;第三種,用 table 標簽。 用哪一種速度最快,代碼少呢?答案是:第三種。多少有點失望啊,HTML5 不是萬能的。詳細代碼如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | this.board=function(name,width,height,rowBak,colBak){ /* 畫棋盤 */
nameBak=name;
if("turnover"==name){row=8;col=8;}else if("gogame"==name){row=18;col=18;}
var aW=Math.floor(width/(col+2)),aH=Math.floor(height/(row+2));
minL=(aW>aH?aH:aW)-4;// 這個減法很重要,否則填空時會把表格撐大
var array=new Array("< div style=\"margin:"+minL+"px;\"> "+
"< table border = 1 cellspacing = 0 width=\""+(aW*col)+"\"
height=\""+(aH*row)+"\">");
for(var i=0;i< row ;i++){
array.push("<tr>");
for(var j=0;j< col ;j++){array.push("<td align = center >"+
evt(i,j,minL,minL,aW*j+minL/2+8,aH*i+minL/2)+"</ td >");}
if(nameBak!="four"&&nameBak!="turnover")/* 將事件添加到表格中 */
array.push(evt(i,col,minL,minL,aW*col+minL/2+8,aH*i+minL/2));
array.push("</ tr >");
}
if(nameBak!="four"&&nameBak!="turnover"){
for(var j=0;j<=col;j++){
array.push(evt(row,j,minL,minL,aW*j+minL/2+8,aH*row+minL/2));
}
}
document.write(array.join("")+"</ table ></ div >");
setClick(row,col,minL,minL);/* 初始化事件 */
start();/* 初始化棋子 */
}
|
上面代碼中,最重要的是標黑體的第 6 行代碼,這里面有兩個訣竅,第一個就是 table 的定義,第二個就是使用了 Array 數(shù)組。為什么要使用數(shù)組,而不是定義一個字符串呢?答案是優(yōu)化,就是 Array 數(shù)組的 push 方法的速度要遠遠快于 String 字符串的加 + 運算。共計 16 行代碼,一個棋盤就畫好了,當然這其中不僅僅是畫線,還有棋子處理,事件定義等方法的調(diào)用,后面將陸續(xù)談到。 繪制棋子繪制完棋盤,我們來繪制棋子。我們挑選的這四種棋,雖然棋盤不同,但是棋子都是相同的,都是黑白棋子。這在以前,做在線對弈,除了 Flash 能實現(xiàn)美觀效果外,其他的必須先請美工做幾副小圖片,HTML5 時代,美工的人力和溝通成本就節(jié)省了。 我們至少有兩種方法繪制棋子,第一種是:canvas 類,第二種就是 css 的圓角屬性。用哪種速度又快代碼又少呢?答案是第二種,圓角。代碼如下: 1 2 3 4 5 6 7 8 9 10 11 | function man(width,height,id,colorBak){ /* 畫棋子 */
var color=colorBak==null?(order++%2==0?"000":"CCC"):colorBak;
var r="border-radius:"+width/2+"px;";
var obj=id==null?event.srcElement:_$(id);
obj.innerHTML="< div id=\"man_"+color+"_"+order+"\" style=\"display:block;-webkit-"
+r+"-moz-"+r+""+r+"-moz-box-shadow:inset 0 -10px 40px rgba(0,0,0,1);"+
"box-shadow:inset 0 -10px 40px rgba(0,0,0,1);"+
"background:-webkit-gradient(radial, 50 40, 30, center center, 80, from(#"+color+"),
to(rgba(255,255,255,1)));"+
"width:"+width+"px;height:"+height+"px;\"></ div >";
}
|
上面代碼中,我們看到,我們將每一個棋子定義了一個 DIV,使用了 CSS3 的 shadow,gradient 屬性,并且可以根據(jù)棋盤的大小自動計算棋子的大小,另外,如果用戶不喜歡黑白顏色,甚至可以定義成紅黃顏色,女生和小朋友估計會喜歡。這 5 行代碼是畫一個棋子的方法,做一個簡單的循環(huán),就可以畫出多個棋子,方法如下。 1 2 3 | function moreMan(array){for(var i=0;i<array.length;i++)
man(minL,minL,nameBak+"_"+array[i]);}
/* 繪制多個棋子 */
|
處理事件 繪制完棋盤和棋子,我們來分析一下用戶的動作。用戶的動作無非就是兩種,一種是點擊棋盤 table,另外一種就是點擊棋子 DIV。難點在點擊 table 這里,我們要獲知用戶點擊 table 的位置。 傳統(tǒng)思路可能是這樣,使用 event 方法,獲得 x,y 的坐標,然后與 table 的左上角做減法,然后再跟單元格 cell 做除法。聽起來都麻煩。 如果您仔細閱讀了前面的代碼,就應該發(fā)現(xiàn),其實在畫棋盤是,我們向 array 數(shù)組中 push 了一個 evt 方法,很明顯,這個 evt 方法要返回一個字符串變量的,那么他的內(nèi)容是什么呢?答案揭曉: 1 2 3 4 5 6 | function evt(i,j,width,height,left,top){ /* 單一單元格事件 */
return "< div id=\""+nameBak+"_"+i+"_"+j+"\" style=\"position:"+
(nameBak=="four"||nameBak=="turnover"?"block":"absolute")+
";border:0px solid #000;width:"+
width+"px;height:"+height+"px;top:"+top+"px;left:"+left+"px;\"></ div >";
}
|
原理是一個 DIV。對了,這個添加事件的方法非常特殊,實際上是在每個棋盤的交叉的地方畫了一個 DIV,然后給 DIV 添加事件。 1 2 3 4 5 6 7 8 | function setClick(row,col,width,height){
for(var i=0;i<=row;i++){
for(var j=0;j<=col;j++){
var els=_$(nameBak+"_"+i+"_"+j);
if(els!=null)els.onclick=function(){if(rule())man(width,height);};
}
}
}
|
需要說明的是,DIV 一定要先定義,即 document.write 輸出出來,然后才能執(zhí)行 onclick 的定義,否則會返回 DIV 未定義的錯誤。寥寥 10 行代碼,把事件問題搞定了。 落子規(guī)則前面說了,用戶點擊事件有兩種,點擊棋盤 table 事件我們采用額外增加 DIV 的方法巧妙解決了,第二種點擊棋子的方法又該如何呢? 先要說明的是,點擊棋子其實是一種錯誤的事件,點擊棋盤可以落子,點擊棋子是什么意思?黑白棋點擊棋子是無意義的,我們必須要進行判斷,不能在有子的地方落子,這是規(guī)則之一。所以必須要定義一個方法,判斷是不是點擊的地方是不是有棋子。代碼如下: 1 2 3 4 5 | function isMan(row,col){var obj=_$(nameBak+"_"+row+"_"+col,1);
if(obj==null||obj.indexOf("man_")==-1)return null;
else if(obj.indexOf("000")!=-1)
return 0;
else if(obj.indexOf("CCC")!=-1)return 1;}
|
想不到吧,其實只要一行代碼就可以就可以做是否有子的判斷,怎么判斷的,訣竅就在于判斷 DIV 的顏色,棋子要么黑,返回 0,要么白,返回 1,但是空白地方是沒有顏色的,返回 null。這里要特別注意返回值,后面判斷輸贏的時候還要用,所以不能簡單通過 true 或者 false 的的返回值來判斷是否有子,而是要判斷出有什么顏色的子。 對于五子棋和圍棋,這一條規(guī)則夠用了,但是對于翻轉(zhuǎn)棋和四子棋,還有第二條規(guī)則:不能在四周空白的地方落子,就是說必須是相連的。也就是說,不僅僅要判斷點擊的地方是不是有棋子,還要判斷其四周是不是有棋子,這個,不是可以有,而是,必須有。需要做一個小循環(huán)啊,代碼如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | function rule(){/* 走棋規(guī)則 */
var id=event.srcElement.id;
if(id.indexOf("man_")==0){alert("不能在有子的地方落子");return false;}else{
var p=id.indexOf("_"),p1=id.lastIndexOf("_");
var row=id.substr(p+1,p1-p-1)*1,col=id.substr(p1+1)*1;
if("gobang"==nameBak)return gobang(row,col);
else if("four"==nameBak){
if(isMan(row,col+1)==null&&isMan(row,col-1)==null&&
isMan(row+1,col)==null&&
isMan(row-1,col)==null){
alert("四子棋不能在四周空白的地方落子!");
return false;
}
return gobang(row,col,3);
}else if("turnover"==nameBak){
if(isMan(row,col+1)==null&&isMan(row,col-1)==null&&
isMan(row+1,col)==null&&isMan(row-1,col)==null&&
isMan(row-1,col-1)==null&&
isMan(row+1,col+1)==null){
alert("翻轉(zhuǎn)棋不能在四周空白的地方落子!");
return false;
}
turnover();
}else if("gogame"==nameBak){
}
}
return true;
}
|
循環(huán)中,反復調(diào)用 isMan 方法判斷是否有棋子,所以如果 isMan 寫得不夠簡練,快速,不知道要耗費多少時間啊。數(shù)一數(shù),總共 19 行代碼就處理了落子規(guī)則。 到這里,我們繪制了棋盤,棋子,獲得了點擊時間,判斷了落子規(guī)則,才用了 40 行左右的代碼,其實程序基本上可用了,但是我們不能滿足啊,還得讓他更加智能一些,我們還需要一個裁判斷輸贏。 判斷輸贏 要判斷輸贏,我們必須要知道下棋的規(guī)則: 五子棋是各個方向的五子相連算贏,四子棋是各個方向四個子相連算贏,翻轉(zhuǎn)棋數(shù)棋子的個數(shù),圍棋則要麻煩些,不僅僅數(shù)棋子個數(shù),還要數(shù)圍住的區(qū)域。 邏輯上好像很復雜啊,似乎也是計算最多的地方,有點人工智能的意思。沒錯,如果前面的基礎打得不好,這里的確要耗費很多代碼,但是因為我們前面定義了 DIV 用顏色判斷是否存在棋子的 iaMan 方法,這里再使用一個小技巧,就可以輕松搞定這個輸贏判斷。先看看五子棋和四子棋的輸贏判斷代碼,然后對照代碼來分析。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | function gobang(row,col,num){
num=num==null?4:num;
var rs=[[],[],[],[]],b=[],w=[];/* 這里采用四維數(shù)組來存儲棋子位置 */
for(var i=0,j=0;i<num*2+1;i++,j++){
rs[0].push(isMan(row-num+i,col));
rs[1].push(isMan(row,col-num+j));
rs[2].push(isMan(row-num+i,col-num+j));
rs[3].push(isMan(row-num+i,col-num+j));
if(i<num){b.push(0);w.push(1);}
}
if(rs.join("#").indexOf(b.join(","))!=-1){alert("黑棋勝");return false;
}else if(rs.join("#").indexOf(w.join(","))!=-1){alert("白棋勝");return false;}
return true;
}
|
共計 9 行代碼就搞定,看懂沒?首先定義了一個 Javascript 多維數(shù)組 rs=[[],[],[],[]],這種定義多維數(shù)組的方法,挑出來重點說明一下,因為搜索引擎上都是搜不到的,我講課時差不多遇到的學生也都不清楚,他們大多采用 new Array,然后加循環(huán)的蝸牛方法。 第二步:從落子的地方開始循環(huán),注意,不是循環(huán)整個棋盤,為的就是節(jié)省時間啊。循環(huán)設計縱橫交叉四個方向,有棋子的地方,就向這個四維數(shù)組 push 棋子的顏色。 第三步:把數(shù)組 join 起來就 ok 啦,如果有 4 個或 5 個 1 相連,自然就是白棋勝,否則就是黑棋勝。 寫道這里,就有點意思啦,注意我們處理的數(shù)據(jù)的方法,我稱之為“塊數(shù)據(jù)”的處理方法,就是充分利用 array 數(shù)組,保存一塊一塊的數(shù)據(jù),無論寫入,讀取,還是統(tǒng)計分析,都是針對這一塊數(shù)據(jù)進行,這樣既可以提高內(nèi)聚度,便于提煉出可以重用的方法,就可以大大的加快執(zhí)行速度。 處理相連都不在話下,數(shù)子就更簡單了,使用塊數(shù)據(jù)處理方法,3 行搞定。 1 2 3 4 5 6 7 8 9 | function turnover(){
if(order< 64 )return;
var num = 0 ;var total = row *col;for(var i = 0 ;i<row;i++){
for(var j = 0 ;j<col;j++){num+=isMan(i+"_"+j);}
}
if(num<total/2)alert("黑棋勝"+(total-num*2)+"子");
else if(num>row*col/2)alert("白棋勝"+(num*2-total)+"子");
else alert("平局");
}
|
棋子初始化 環(huán)環(huán)相扣地寫到這里,還有最后一個關于棋子的問題需要處理。那就是,下五子棋是從空白棋盤開始,其他三種棋卻一開始都是有子的。其實給一個空白棋盤也行,但是其他三種棋因為一般的前幾步走法都是固定的,我們?yōu)榱颂岣咧悄芑潭?,不得不在浪費四行代碼,畢竟,我們的目標是一個市場化的產(chǎn)品,而不是一個初學者不考慮用戶體驗的程序。 1 2 3 4 5 6 | function start(){
if("turnover"==nameBak){moreMan([3+"_"+3,4+"_"+3,4+"_"+4,3+"_"+4]);
}else if("four"==nameBak){man(minL,minL,nameBak+"_"+row/2+"_"+0);
}else if("gogame"==nameBak){moreMan([3+"_"+3,15+"_"+3,15+"_"+15,3+"_"+15]);
}
}
|
其實就是調(diào)用了一下 moreMan 方法,注意也是塊數(shù)據(jù)引用,傳輸了一個數(shù)組,用下劃線分割橫向和縱向坐標。 做成離線應用 本文開頭就說過,臺式電腦的雙人或多人對弈程序早已多如牛毛爛大街了,只有移動應用才能有市場,我們的目標就是奔著這個來的,所以最后必須做成離線應用。 如何實現(xiàn) HTML5 的離線應用,搜索引擎很快能找到結(jié)果,其實只要三個關鍵步驟。 第一步;在 Web 服務器的配置文件中聲明一下。Tomcat 和 Apache 的聲明方式不相同,需要注意; 第二步:定義 manifest 文件,文件格式需要注意; 第三步:在 HTML 的文件中調(diào)用一下 manifest 文件。 根據(jù)這三個步驟,讀者可以自行搜索細節(jié),這里就不贅述了,我只講搜索引擎搜不到的。 另外需要說明的是,iPad 和 Android 平板上瀏覽器實現(xiàn)全屏的方法也不一樣,針對 iPad 用戶,我們還必須定義一行能夠?qū)崿F(xiàn)全屏的代碼。 9. 效果圖、在線演示、開放源代碼本文的在線演示網(wǎng)址是:http://www./chess.htm,效果圖如下圖所示: 圖 1. 效果圖 圖中加了一個選擇棋類型和設置背景功能,如要獲得全部源代碼,只要使用瀏覽器的查看源代碼功能即可,限于篇幅,這里就不貼了。 總結(jié)作為一個程序員,最高的境界不是寫得代碼越多越好,而是用最少的代碼實現(xiàn)最多的計算,解決最多的問題?;叵氘斈?,蓋茨在編寫 Basic 時,為了節(jié)省幾個字符需要絞盡腦汁通宵達旦,以至于遺留了千年蟲世紀難題,反觀今日,在云計算時代,隨著硬盤和內(nèi)存的容量越來越大,CPU 的運算越來越快,很多大型項目的程序員似乎失去了精簡代碼的習慣。但是移動計算的硬件,目前還沒有那么高的配置,本文通過 HTML5 對弈游戲,使用“塊數(shù)據(jù)”計算方法,實現(xiàn)了用最少代碼實現(xiàn)最多計算的目標,特別適用移動計算,與大家共勉。
相關主題
在線演示地址:http://www./chess.htm。 “實現(xiàn) HTML5 和 CSS3 的跨瀏覽器功能”(developerWorks,2012 年 2 月):HTML5 和 CSS3 有許多杰出的新特性,比如能夠離線儲存數(shù)據(jù)和創(chuàng)建無圖像圓角效果。然而并不是所有這些新特性都能夠跨瀏覽器使用。通過本文學習一些能夠在所有主要瀏覽器的最新版本上使用的 HTML5 和 CSS3 技巧,這些瀏覽器包括 Safari、Internet Explorer、Firefox 和 Chrome。 “拖拽:從 Dojo 到 HTML5”(developerWorks,2011 年 2 月):拖拽是 Web 2.0 應用中最流行的技術之一。本文將介紹如何在網(wǎng)絡應用程序中使用 dojo 和 HTML5 這兩種技術的拖拽功能。并將通過示例詳細介紹 HTML5 的拖拽功能。 “HTML5 基礎知識,第 1 部分”(developerWorks,2012 年 6 月):HTML5 代表了 WebSphere Application Server 業(yè)務和云業(yè)務在實現(xiàn)方式上的里程碑式改變。本文是 4 部分系列文章的第 1 部分,該系列旨在介紹 HTML5 的演變,文本首先介紹 HTML5 中新增加的標記和頁面結(jié)構(gòu),提供有關 WebSphere Application Server 頁面設計的高級信息、表單的創(chuàng)建、API 的使用和價值、以及 Canvas 提供的種種創(chuàng)新可能。 “基于 HTML5 的 Dojo Widget 開發(fā)”(developerWorks,2011 年 7 月):本文主要介紹基于 HTML5 來擴展 Dojo Widget,它不僅具有強大的 JavaScript 邏輯控制,而且具有豐富的頁面展現(xiàn)和良好的運行性能。 developerWorks Web development
專區(qū):通過專門關于 Web 技術的文章和教程,擴展您在網(wǎng)站開發(fā)方面的技能。 developerWorks Ajax 資源中心:這是有關 Ajax 編程模型信息的一站式中心,包括很多文檔、教程、論壇、blog、wiki 和新聞。任何 Ajax 的新信息都能在這里找到。 developerWorks Web 2.0 資源中心,這是有關 Web 2.0 相關信息的一站式中心,包括大量 Web 2.0 技術文章、教程、下載和相關技術資源。您還可以通過 Web 2.0 新手入門欄目,迅速了解 Web 2.0 的相關概念。 查看 HTML5 專題,了解更多和 HTML5 相關的知識和動向。
|