JS實(shí)現(xiàn)五子棋——UI篇

介紹內(nèi)容

前些時(shí)間,阿爾法狗對(duì)戰(zhàn)柯潔圍棋大賽很熱門,那只是人工智能中的一個(gè)方向,展示了機(jī)器能代替人做某些事情。
而圍棋是很講究智力的游戲,所以實(shí)現(xiàn)起來(lái)也是很難的,Google花了很多錢這在方面。
那段時(shí)間我也用JS寫了一個(gè)小游戲——五子棋,五子棋相對(duì)來(lái)講簡(jiǎn)單很多。我那時(shí)候在公眾號(hào)上展示給大家,好像大家興趣不大,可能是因?yàn)槲遄悠逵螒蛱^(guò)簡(jiǎn)單,又或者是對(duì)我推的東西不感興趣。那個(gè)公眾號(hào)已經(jīng)被封殺了,現(xiàn)在我又重新開(kāi)了一個(gè)公眾號(hào)。下面有二維碼,如果有興趣可以推一下。
現(xiàn)在我要寫一下教程,你們可以點(diǎn)擊這里體驗(yàn)一番。
這個(gè)實(shí)例完全是用JS實(shí)現(xiàn)的,有UI部分也有AI部分,我們需要有一定的canvas和JS的知識(shí)就足夠了。

  • 棋盤的實(shí)現(xiàn)
    1.canvas繪畫直線。
    2.設(shè)置畫筆的顏色。

  • 棋子的實(shí)現(xiàn)
    1.canvas畫圓。
    2.填充漸變色。

頁(yè)面結(jié)構(gòu)

頁(yè)面上有一個(gè)正方形的棋盤,我們用畫布canvas來(lái)實(shí)現(xiàn)棋盤。

<canvas id="chess" width="450px" height="450px"></canvas>

棋盤有一定的陰影效果,使棋盤更美觀些。我們通過(guò)定義CSS樣式來(lái)實(shí)現(xiàn)。

canvas {
    display: block;
    margin: 50px auto;
    box-shadow: -2px -2px 2px #EFEFEF, 5px 5px 5px #B9B9B9;
}

這個(gè)時(shí)候的效果如下,有一定的陰影效果:

陰影效果

畫棋盤的網(wǎng)格

這里用到JS來(lái)控制canvas畫棋盤,我們知道五子棋的棋盤是由些縱線和橫線組成的,棋盤的樣子如下:

網(wǎng)格的實(shí)現(xiàn)

分別有15條縱線和橫線,每個(gè)格子為30px的正方形,棋盤邊緣有15px的補(bǔ)白。

在棋盤中實(shí)現(xiàn)畫線

在JS中用畫筆畫一條線:

var chess = document.getElementById("chess") ;//獲取canvas
var context = chess.getContext("2d");
context.strokeStyle = "#aaa" ;//畫筆的顏色
context.moveTo(15,15);
context.lineTo(435,15);
context.stroke();

效果:

一條橫線的畫法

開(kāi)始畫棋盤的網(wǎng)線

我們回顧了一下畫線,那接下來(lái)我們用循環(huán)方式畫15條縱線和15條橫線:

var chess = document.getElementById("chess") ;//獲取canvas
var context = chess.getContext("2d");

context.strokeStyle = "#aaa" ;//畫筆的顏色

for (var i=0; i<15; i++) {//通過(guò)循環(huán)畫網(wǎng)格
    context.moveTo(15,15+i*30);
    context.lineTo(435,15+i*30);
    context.stroke();
    context.moveTo(15+i*30,15);
    context.lineTo(15+i*30,435);
    context.stroke();
}

效果:

完成網(wǎng)格

棋盤背景

可能你已經(jīng)發(fā)現(xiàn)了,白色的棋盤背景視覺(jué)非常不好,那么接下來(lái)我們就來(lái)為棋盤添加背景。我們選擇一張木色的圖片,如果你想為棋盤添加你特有的水印,可以通過(guò)制圖軟件添加。
H5添加圖片的方法是通過(guò)畫圖的方式,畫上去就會(huì)覆蓋掉之前畫的網(wǎng)格,所以我們通過(guò)對(duì)畫網(wǎng)格的代碼進(jìn)行封裝成一個(gè)函數(shù),畫完背景后再調(diào)用畫網(wǎng)格的函數(shù)來(lái)達(dá)到不被覆蓋的效果。總的代碼如下:

var img = new Image();
img.src = "img/2.png" ; 
img.onload = function (){
    context.drawImage(img,0,0,450,450);
    drawLine();
}
function drawLine () {//把畫線封裝成函數(shù)
    for (var i=0; i<15; i++) {//通過(guò)循環(huán)畫網(wǎng)格
        context.moveTo(15,15+i*30);
        context.lineTo(435,15+i*30);
        context.stroke();
        context.moveTo(15+i*30,15);
        context.lineTo(15+i*30,435);
        context.stroke();
    }
}

最終的效果:

棋盤效果

畫棋子

我們是在背景上畫棋子的,所以畫棋子的代碼應(yīng)該放在onload方法里面。

棋子的畫法

    context.beginPath() ;
    context.arc(200,200,100,0,2*Math.PI);
    context.closePath() ;
    context.fill();

解釋一下特別的代碼context.arc(200,200,100,0,2*Math.PI);四個(gè)參數(shù)分別是圓心橫坐標(biāo)、圓心縱坐標(biāo)、半徑、開(kāi)始弧度、結(jié)束弧度。
context.fill();給圓填充顏色。
效果:

一個(gè)棋子

這個(gè)效果還不像棋子,棋子中間要有些發(fā)亮才行的,我們給棋子中間加一個(gè)亮度的漸變:
我們直接看onload方法里的代碼,再解釋其中重要的代碼:

img.onload = function (){
    context.drawImage(img,0,0,450,450);
    drawLine();
    //畫棋子  
    context.beginPath() ;
    context.arc(200,200,100,0,2*Math.PI);
    context.closePath() ;
    var gradient = context.createRadialGradient(200, 200, 50, 200, 200, 20);
    gradient.addColorStop(0, "#0a0a0a");
    gradient.addColorStop(1, "#636766");
    context.fillStyle = gradient ;
    context.fill();
}

這些代碼的操作非常簡(jiǎn)單,首先畫一個(gè)圓,然后填充漸變色,var gradient = context.createRadialGradient(200, 200, 50, 200, 200, 20);這是定義一個(gè)有漸變的顏色變量,前三個(gè)參數(shù)是圓心在(200,200)處,半徑為50的一個(gè)圓,同理,后三個(gè)參數(shù)是圓心在(200,200)處,半徑為20的一個(gè)圓。

gradient.addColorStop(0, "#0a0a0a");
gradient.addColorStop(1, "#636766");
context.fillStyle = gradient ;

這三行代碼分別設(shè)置上面的第一個(gè)圓的顏色,第二個(gè)圓的顏色,和把漸變色填充給棋子。最終的填充效果是在圓心為(200,200)內(nèi)徑為20,外徑為50的一個(gè)圓環(huán)上產(chǎn)生漸變。
棋子最后的效果:

中間發(fā)亮

落子樣式

那我們通過(guò)上面的學(xué)習(xí)就會(huì)畫一個(gè)棋子啦,接下來(lái)我們改變棋子的半徑大小和顏色就能得到我們想要的棋子了。
代碼放在onload里面會(huì)顯得很雜亂,這是我們不想看到的,所以我們必須封裝成函數(shù)再使用。
封裝成以下的函數(shù):

var oneStep = function (i, j, me){//i,j分別是在棋盤中的定位,me代表白棋還是黑棋
    context.beginPath() ;
    context.arc(15+i*30, 15+j*30, 13, 0, 2*Math.PI);//圓心會(huì)變的,半徑改為13
    context.closePath() ;
    var gradient = context.createRadialGradient(15+i*30+2, 15+j*30-2, 15, 15+i*30, 15+j*30, 0);
    if(me){
        gradient.addColorStop(0, "#0a0a0a");
        gradient.addColorStop(1, "#636766");
    }else{
        gradient.addColorStop(0, "#D1D1D1");
        gradient.addColorStop(1, "#F9F9F9");
    }
    context.fillStyle = gradient ;
    context.fill();
}

主要改變了三部分,改變圓心和半徑,根據(jù)接收到的參數(shù)確定圓心,判斷是黑子還是白子。
然后通過(guò)在onload方法里調(diào)用函數(shù)來(lái)落子:

oneStep(0,0,true) ;
oneStep(1,1,false) ;

效果:

改變棋子大小

實(shí)現(xiàn)鼠標(biāo)落子

實(shí)現(xiàn)用鼠標(biāo)點(diǎn)擊棋盤就落下一顆棋子,我們用在畫布上綁定單擊事件來(lái)實(shí)現(xiàn),代碼如下:

var me = true ;
chess.onclick = function (e){
    var x = e.offsetX ;
    var y = e.offsetY ;
    var i = Math.floor(x/30) ;
    var j = Math.floor(y/30) ;
    oneStep(i,j,me);
    me = !me ;
}

通過(guò)e.offsetXe.offsetY兩個(gè)屬性得到坐標(biāo),后轉(zhuǎn)化成i和j,再調(diào)用oneStep()方法,定義一個(gè)變量me來(lái)決定是黑子還是白子,每點(diǎn)擊一次就改變一次me的值。
效果:

鼠標(biāo)落子

vzsf
這時(shí)候還有一個(gè)問(wèn)題,已經(jīng)下了黑子的點(diǎn),重新點(diǎn)擊還會(huì)被白子覆蓋掉。那怎么解決呢?
首先我們定義一個(gè)二維數(shù)組,存放所有的落子點(diǎn),如果有落子,就給其記錄下來(lái)。落子的時(shí)候再判斷是否已經(jīng)落子,如果已經(jīng)落子了就不允許重新落子。思路就是這樣。
二維數(shù)組代碼:

var chessBoard = [] ;
for (var i=0; i<15; i++) {
    chessBoard[i] = [] ;
    for (var j=0; j<15; j++) {
        chessBoard[i][j] = 0;
    }
}

二維數(shù)組的初始值都是0,然后在單擊事件的方法里添加一個(gè)判斷:

chess.onclick = function (e){
    var x = e.offsetX ;
    var y = e.offsetY ;
    var i = Math.floor(x/30) ;
    var j = Math.floor(y/30) ;
    if(chessBoard[i][j] == 0){
        oneStep(i,j,me);
        if(me){
            chessBoard[i][j] = 1 ;
        }else{
            chessBoard[i][j] = 2 ;
        }
        me = !me ;  
    }
}

落子位置等于0才可以落子,落完子后給相應(yīng)的點(diǎn)附非0值,黑子就附1,白子附2。
效果:

落子不能覆蓋

總結(jié)

這是UI篇,你繼續(xù)翻翻我的主頁(yè),肯定能找到AI篇的。

  • 棋盤的實(shí)現(xiàn)
    通過(guò)循環(huán)畫直線

  • 棋子的實(shí)現(xiàn)
    畫出你想要的棋子,漸變填充顏色,封裝成一個(gè)函數(shù)供調(diào)用。

  • 落子的實(shí)現(xiàn)
    用數(shù)組存放每一個(gè)落子點(diǎn),滿足條件就落下對(duì)應(yīng)的子。

UI篇到此就告一段落了,這里用到的知識(shí)并不多,相應(yīng)的方法想了解更多可以到W3上看。AI篇將會(huì)在后期推出,到時(shí)候就可以實(shí)現(xiàn)人機(jī)交互了。跟自己打的代碼比試五子棋不再只是一種想法,只要你動(dòng)手,一定能實(shí)現(xiàn)。另外,想跟我的代碼比試一下可以點(diǎn)擊這里。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 為什突然做這個(gè),因?yàn)檫@是個(gè)筆試題,拖了一個(gè)月才寫(最近終于閑了O(∩_∩)O),廢話不多說(shuō),說(shuō)說(shuō)這個(gè)題吧 題目要求...
    Stevenzwzhai閱讀 2,820評(píng)論 0 5
  • 最近一直在學(xué)習(xí)Android自定義View方面的知識(shí),正好看到一個(gè)講解制作五子棋小游戲的案例,遂學(xué)習(xí)一番,記錄下學(xué)...
    冰鑒IT閱讀 3,278評(píng)論 5 16
  • 一:canvas簡(jiǎn)介 1.1什么是canvas? ①:canvas是HTML5提供的一種新標(biāo)簽 ②:HTML5 ...
    GreenHand1閱讀 4,874評(píng)論 2 32
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,720評(píng)論 25 709
  • 20161203 11:00 業(yè)果不會(huì)錯(cuò)亂?!灸罘痣s話】 12:00 恭敬,來(lái)自內(nèi)心的渴求。【念佛雜話】 13:0...
    整點(diǎn)念佛閱讀 281評(píng)論 0 0

友情鏈接更多精彩內(nèi)容