閒情奕趣(基於html5的圍棋應用)

一、閒情奕趣

  少時,聞奕而不知奕之趣,觀棋而不識棋之髓。近日,略習奕之規矩,演練一二,始覺其妙。今見各手談之軟件,心生一念,自編一演習軟件,以調閒暇之情,培對弈之趣,故取一名,曰:“閒情奕趣”。

——雪飄七月 

  近日忙裏偷閒得以編寫此對弈軟件,以HTML5爲基礎,canvas畫布繪製展示棋盤棋子,localStorage本地存儲本局的各個步驟。今日程序初具雛形,寫此日誌,以供大家交流學習。

下面上圖一張:

二、棋佈星羅

下面就來講棋盤棋子的繪製,我們的繪製都是在canvas中一條線一個圓地繪製成的。

棋盤是19*19的線條與9個星位組成,9個星位就是9個以星位爲圓心的圓。

棋子的繪製也是畫圓,只是圓半徑較星位大,而棋子是通過一個19*19的數組存儲標記位來實現的,數組中361個值與棋盤上的361個位置一一對應。若數值爲0,表示沒有落子;數值若爲1,表示黑方落子;數值若爲2,表示白方落子。

  1. //獲取canvas畫布 
  2. var canvas=document.getElementById('myCanvas'); 
  3. canvas.height = total_height; 
  4. canvas.width = total_width; 
  5. var cxt=canvas.getContext('2d'); 
  6.  
  7. //畫棋盤 
  8. var drawBoard = function(){ 
  9.     //每次重畫棋盤之前清楚canvas 
  10.     cxt.clearRect(0, 0, canvas.width, canvas.height); 
  11.  
  12.     cxt.beginPath(); 
  13.     //描繪橫線 
  14.     for(var i = 0 ; i < 19 ; i++){ 
  15.         var start_company_x = chessboard_start_x; 
  16.         var start_company_y = chessboard_start_y + company_y*i; 
  17.         var end_company_x = chessboard_end_x; 
  18.         var end_company_y = chessboard_start_y + company_y*i; 
  19.         cxt.lineWidth = 2; 
  20.         cxt.moveTo(start_company_x,start_company_y); 
  21.         cxt.lineTo(end_company_x,end_company_y); 
  22.     } 
  23.  
  24.     //描繪豎線 
  25.     for(var j = 0 ; j < 19 ; j++){ 
  26.         var start_company_x = chessboard_start_x + company_x*j; 
  27.         var start_company_y = chessboard_start_y; 
  28.         var end_company_x = chessboard_start_x + company_x*j; 
  29.         var end_company_y = chessboard_end_y; 
  30.         cxt.moveTo(start_company_x,start_company_y); 
  31.         cxt.lineTo(end_company_x,end_company_y); 
  32.     } 
  33.     cxt.stroke(); 
  34.  
  35.     //畫九星 
  36.     for(var i = 0 ; i < 3 ; i ++ ){ 
  37.         var star_y = chessboard_start_y + company_y*(i*6+3); 
  38.         for(var j = 0 ; j < 3 ; j++){ 
  39.             var star_x = chessboard_start_x + company_y*(j*6+3); 
  40.             cxt.fillStyle="black"
  41.             cxt.beginPath(); 
  42.             cxt.arc(star_x, star_y, 4, 0, Math.PI*2, false); 
  43.             cxt.stroke(); 
  44.             cxt.fill(); 
  45.         } 
  46.     } 
  47.  
  48. //根據arr數組畫棋子 
  49. var drawPiece = function(){ 
  50.     for(var i in arr){ 
  51.         for(var j in arr[i]){ 
  52.             if(arr[i][j] == 1){ 
  53.                 //畫黑子 
  54.                 var start_x = i*company_x + chessboard_start_x;//點擊的x座標起始位置 
  55.                 var start_y = j*company_y + chessboard_start_y;//點擊的y座標起始位置 
  56.  
  57.                 cxt.fillStyle= "black"
  58.                 cxt.beginPath(); 
  59.                 cxt.arc(start_x, start_y, piece_radius, 0, Math.PI*2, false); 
  60.                 cxt.stroke(); 
  61.                 cxt.fill(); 
  62.             } else if(arr[i][j] == 2){ 
  63.                 //畫白子 
  64.                 var start_x = i*company_x + chessboard_start_x;//點擊的x座標起始位置 
  65.                 var start_y = j*company_y + chessboard_start_y;//點擊的y座標起始位置 
  66.  
  67.                 cxt.fillStyle= "white"
  68.                 cxt.beginPath(); 
  69.                 cxt.arc(start_x, start_y, piece_radius, 0, Math.PI*2, false); 
  70.                 cxt.stroke(); 
  71.                 cxt.fill(); 
  72.             } 
  73.         } 
  74.     } 

三、旁觀者清

當局者迷,旁觀者清。

這一塊其實我想說的是對弈的步驟,當然不是真的對弈的步驟,而是對弈程序中對弈提子的算法。

下面這塊代碼就是用來計算落子處是否可以提子的方法,

首先,方法中依次判斷該子上方、左方、右方、下方的棋子是否能夠被提,如果可以被提,就提子,然後返回true,這個方法的名稱是checkOthersidePiece(x,y,side);

然後,查看本子是否會導致本方棋子被提,若會被提,提子,然後返回true,這個方法依然是checkOthersidePiece(x,y,side);

  1. //檢查該落子是否可以提子,可以就提子 
  2. var checkPiece = function(coordinate_x,coordinate_y){ 
  3.     var myside = 0; 
  4.     var otherside = 0; 
  5.     var iskill = false
  6.     if(side == "black"){ 
  7.         myside = 1; 
  8.         otherside = 2; 
  9.     }else if(side == "white"){ 
  10.         myside = 2; 
  11.         otherside = 1; 
  12.     } 
  13.  
  14.     //如果該子上方是對方棋子或者爲第一行 
  15.     if((coordinate_y>0 && arr[coordinate_x][coordinate_y-1] == otherside)){ 
  16.         if(checkOthersidePiece(coordinate_x,coordinate_y-1,otherside)){ 
  17.             iskill = true
  18.         } 
  19.     } 
  20.     //如果該子左方是對方棋子或者爲第一行 
  21.     if((coordinate_x>0 && arr[coordinate_x-1][coordinate_y] == otherside)){ 
  22.         if(checkOthersidePiece(coordinate_x-1,coordinate_y,otherside)){ 
  23.             iskill = true
  24.         } 
  25.     } 
  26.     //如果該子右方是對方棋子或者爲第十九行 
  27.     if((coordinate_x<18 && arr[coordinate_x+1][coordinate_y] == otherside)){ 
  28.         if(checkOthersidePiece(coordinate_x+1,coordinate_y,otherside)){ 
  29.             iskill = true
  30.         } 
  31.     } 
  32.     //如果該子下方是對方棋子或者爲第十九行 
  33.     if((coordinate_y<18 && arr[coordinate_x][coordinate_y+1] == otherside)){ 
  34.         if(checkOthersidePiece(coordinate_x,coordinate_y+1,otherside)){ 
  35.             iskill = true
  36.         } 
  37.     } 
  38.  
  39.     //如果有提掉對方棋子 
  40.     if(iskill == true){ 
  41.  
  42.     //監測本子落子後己方棋子是否會被提 
  43.     }else { 
  44.         //alert(JSON.stringify(arr)); 
  45.         checkOthersidePiece(coordinate_x,coordinate_y,myside); 
  46.     } 

接下來我們就來看看這個神通廣大的checkOthersidePiece(x,y,side);方法

該方法中新建一個19*19的數組,然後從該棋子位置循環遍歷上、左、右、下的棋子,

如果周邊棋子有空的,那將該空的位置標誌位改爲3;

如果周邊棋子爲己方的繼續遍歷該已方棋子的周邊棋子,循環往復,直到遍歷完與本子相連的已方棋子

  1. /** 
  2.  * 檢查該子是否會被提,若能被提,提子 
  3.  * @param coordinate_x (該子x座標) 
  4.  * @param coordinate_y (該子y座標) 
  5.  * @param side 1(黑子) or 2(白子) 
  6.  */ 
  7. var checkOthersidePiece = function(coordinate_x,coordinate_y,side){ 
  8.     //新建一個二維數組,用於排放與該子連接的本方棋子 
  9.     var connection_arr = new Array(19); 
  10.     for(var i = 0 ; i < 19 ; i++){ 
  11.         var connection_arrj = new Array(19); 
  12.         for(var j = 0 ; j < 19 ; j++){ 
  13.             connection_arrj[j] = 0; 
  14.         } 
  15.         connection_arr[i] = connection_arrj; 
  16.     } 
  17.     var isdead = true;//是否被提,默認爲被提 
  18.     var deadcount = 0; 
  19.  
  20.     //alert("1111"+JSON.stringify(connection_arr)+coordinate_x+":"+coordinate_y); 
  21.     //將所有與本子相連的同色棋子組成本數組 
  22.     connection_arr = setconnection_arr(connection_arr,coordinate_x,coordinate_y,side); 
  23.     //alert("2222"+JSON.stringify(connection_arr)); 
  24.  
  25.     //遍歷該connection_arr數組,若有3則,不死 
  26.     for(var i in connection_arr){ 
  27.         for(var j in connection_arr[i]){ 
  28.             if(connection_arr[i][j] == 3){ 
  29.                 //console.log("i+j:"+i+"+"+j); 
  30.                 isdead = false
  31.             } 
  32.         } 
  33.     } 
  34.  
  35.     //如果會被提,則提子 
  36.     if(isdead == true){ 
  37.         for(var i in connection_arr){ 
  38.             for(var j in connection_arr[i]){ 
  39.                 if(connection_arr[i][j] == side){ 
  40.                     arr[i][j] = 0; 
  41.                     deadcount++; 
  42.                 } 
  43.             } 
  44.         } 
  45.     } 
  46.     console.log("isdead:"+isdead+ ":提子數量:"+ deadcount); 
  47.     //若有提子返回true 
  48.     if(deadcount > 0){ 
  49.         return true
  50.     }else { 
  51.         return false
  52.     } 

再然後,我們來看一看這個遞歸遍歷的方法吧setconnection_arr(arr,x,y,side);

在本方法中做的就是將本落子周圍的己方棋子添加到connection_arr中,同時把周圍的氣以3爲標誌位添加到connection_arr中。

  1. /** 
  2.  * 遞歸組織連接的此方棋子 
  3.  * @param connection_arr 
  4.  * @param coordinate_x 
  5.  * @param coordinate_y 
  6.  * @param side 
  7.  * @return {*} 
  8.  */ 
  9. var setconnection_arr = function(connection_arr,coordinate_x,coordinate_y,side){ 
  10.     //設置數組爲本塊相連的 
  11.     if(connection_arr[coordinate_x][coordinate_y] != side && arr[coordinate_x][coordinate_y] == side){ 
  12.         connection_arr[coordinate_x][coordinate_y] = side; 
  13.         console.log(coordinate_x + "====" + coordinate_y); 
  14.         //如果該黑子上方是白子並且不爲第一行 
  15.         if(coordinate_y>0 && arr[coordinate_x][coordinate_y-1] == side){ 
  16.             connection_arr = setconnection_arr(connection_arr,coordinate_x,coordinate_y-1,side); 
  17.         }else if(coordinate_y>0 && arr[coordinate_x][coordinate_y-1] == 0){ 
  18.             connection_arr[coordinate_x][coordinate_y-1] = 3; 
  19.         } 
  20.         //如果該黑子左方是白子或者爲第一行 
  21.         if(coordinate_x>0 && arr[coordinate_x-1][coordinate_y] == side){ 
  22.             connection_arr = setconnection_arr(connection_arr,coordinate_x-1,coordinate_y,side); 
  23.         }else if(coordinate_x>0 && arr[coordinate_x-1][coordinate_y] == 0){ 
  24.             connection_arr[coordinate_x-1][coordinate_y] = 3; 
  25.         } 
  26.         //如果該黑子右方是白子或者爲第十九行 
  27.         if(coordinate_x<18 && arr[coordinate_x+1][coordinate_y] == side){ 
  28.             connection_arr = setconnection_arr(connection_arr,coordinate_x+1,coordinate_y,side); 
  29.         }else if(coordinate_x<18 && arr[coordinate_x+1][coordinate_y] == 0){ 
  30.             connection_arr[coordinate_x+1][coordinate_y] = 3; 
  31.         } 
  32.         //如果該黑子下方是白子或者爲第十九行 
  33.         if(coordinate_y<18 && arr[coordinate_x][coordinate_y+1] == side){ 
  34.             connection_arr = setconnection_arr(connection_arr,coordinate_x,coordinate_y+1,side); 
  35.         }else if(coordinate_y<18 && arr[coordinate_x][coordinate_y+1] == 0){ 
  36.             connection_arr[coordinate_x][coordinate_y+1] = 3; 
  37.         } 
  38.     } 
  39.  
  40.     //console.log("x:"+coordinate_x+"y:"+coordinate_y); 
  41.     return connection_arr; 

 

四、白璧微瑕

現在雖然可以順暢地落子了,但是對於一些規則還是沒有作限制,比如說:

1、剛被吃掉一子的地方不可立馬落子(這需要添加一個機制)

2、不可悔棋,雖說落子無悔大丈夫,只是不小心點錯位置導致整盤棋都廢掉,實在有點可惜(這個可以引入數據庫來解決)

等等等等,這些我會在之後的時間裏慢慢改進

將這些能想到的改進之後,打算以socketio爲基礎做一個在線的對弈的小程序。

最後,歡迎各位IT大神以及各位喜歡圍棋的大神們不吝賜教……

當然,有問題的也歡迎多多提問……

…………

什麼?源碼?好吧,點擊下面的附件go.rar,下載到本地以chrome或firefox打開canvasgo.html即可跑起來啦!

效果?想看效果?給你個傳送門吧!記得用chrome或firefox打開。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章