介紹內(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)的樣子如下:
分別有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();
效果:
開(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();
}
效果:
棋盤(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();
}
}
最終的效果:
畫(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è)效果還不像棋子,棋子中間要有些發(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)生漸變。
棋子最后的效果:
落子樣式
那我們通過(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.offsetX
和e.offsetY
兩個(gè)屬性得到坐標(biāo),后轉(zhuǎn)化成i和j,再調(diào)用oneStep()
方法,定義一個(gè)變量me來(lái)決定是黑子還是白子,每點(diǎn)擊一次就改變一次me的值。
效果:
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)擊這里。