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

介紹內(nèi)容

前些時(shí)間,阿爾法狗對(duì)戰(zhàn)柯潔圍棋大賽很熱門(mén),那只是人工智能中的一個(gè)方向,展示了機(jī)器能代替人做某些事情。
而圍棋是很講究智力的游戲,所以實(shí)現(xiàn)起來(lái)也是很難的,Google花了很多錢(qián)這在方面。
那段時(shí)間我也用JS寫(xiě)了一個(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)在我要寫(xiě)一下教程,你們可以點(diǎn)擊這里體驗(yàn)一番。
這個(gè)實(shí)例完全是用JS實(shí)現(xiàn)的,有UI部分也有AI部分,我們需要有一定的canvas和JS的知識(shí)就足夠了。

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

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

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

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

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

棋盤(pán)有一定的陰影效果,使棋盤(pán)更美觀(guān)些。我們通過(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í)候的效果如下,有一定的陰影效果:

陰影效果

畫(huà)棋盤(pán)的網(wǎng)格

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

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

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

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

在JS中用畫(huà)筆畫(huà)一條線(xiàn):

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

效果:

一條橫線(xiàn)的畫(huà)法

開(kāi)始畫(huà)棋盤(pán)的網(wǎng)線(xiàn)

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

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

context.strokeStyle = "#aaa" ;//畫(huà)筆的顏色

for (var i=0; i<15; i++) {//通過(guò)循環(huán)畫(huà)網(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)格

棋盤(pán)背景

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

var img = new Image();
img.src = "img/2.png" ; 
img.onload = function (){
    context.drawImage(img,0,0,450,450);
    drawLine();
}
function drawLine () {//把畫(huà)線(xiàn)封裝成函數(shù)
    for (var i=0; i<15; i++) {//通過(guò)循環(huán)畫(huà)網(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();
    }
}

最終的效果:

棋盤(pán)效果

畫(huà)棋子

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

棋子的畫(huà)法

    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();
    //畫(huà)棋子  
    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)單,首先畫(huà)一個(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ì)畫(huà)一個(gè)棋子啦,接下來(lái)我們改變棋子的半徑大小和顏色就能得到我們想要的棋子了。
代碼放在onload里面會(huì)顯得很雜亂,這是我們不想看到的,所以我們必須封裝成函數(shù)再使用。
封裝成以下的函數(shù):

var oneStep = function (i, j, me){//i,j分別是在棋盤(pán)中的定位,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)擊棋盤(pán)就落下一顆棋子,我們用在畫(huà)布上綁定單擊事件來(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篇的。

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

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

  • 落子的實(shí)現(xiàn)
    用數(shù)組存放每一個(gè)落子點(diǎn),滿(mǎ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)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,606評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,044評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,227評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,447評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評(píng)論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,667評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,930評(píng)論 1 287
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,709評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容

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