記得讀大學時,有段時間特別喜歡和室友們下五子棋,因爲腦子不是特別靈光,再加上室友確實經驗豐富,本身天然是屢屢戰敗。時光荏苒,一眨眼好多年過去了,非常懷念那時愜意的時光!大學畢業後,室友們都從事了不一樣行業的工做,我也是如願選擇了作「程序員」。AI近些年來一直很火熱,其餘行業的小夥伴老是誤認爲咱們很厲害的樣子,卻不知「如魚飲水,冷暖自知」。不過「專業思想」還確實是有的,因此有感寫了這篇博文,和你們就五子棋探討一下「計算機」思惟和「人類」思惟的區別和聯繫。話很少說,先上一張「戰敗」效果圖(我認可我仍是下不過本身的智能算法):點擊查看源代碼:
git
五子棋的棋盤很簡單,不過是一些橫豎交叉的線條而已。懂canvas的同窗天然知道是怎麼回事:設計好座標以後,在畫布上畫15條橫豎交叉的線就能夠了,也是橫x,豎y,兩個for循環而已。上代碼:程序員
context.strokeStyle = "#BFBFBF"; //設置線條顏色 function drawChessBoard() { //繪製棋盤 for(var i=0; i<15; i++) { //橫線條 context.moveTo(20 + i*40, 20); context.lineTo(20 + i*40, 580); context.stroke(); //豎線條 context.moveTo(20, 20 + i*40); context.lineTo(580, 20 + i*40); context.stroke(); } }
「棋子」是圓的,有黑、白兩種顏色。只要咱們找好圓心點,設置好半徑,顏色就能夠了。爲了使棋子看起來更加逼真,能夠給棋子中心添加漸變光澤,也不過是調用一個函數,封裝成函數代碼以下:github
function drawChessMan(i, j) { //繪製棋子 context.beginPath(); context.arc(20 + i*40, 20 + j*40, 18, 0, 2*Math.PI); context.closePath(); var gradient = context.createRadialGradient(23 + i*40, 17 + j*40, 18, 23 + i*40, 17 + j*40, 0); if(curColor === 'white') { gradient.addColorStop(0, "#D1D1D1"); gradient.addColorStop(1, "#F9F9F9"); } if(curColor === 'black') { gradient.addColorStop(0, "#0A0A0A"); gradient.addColorStop(1, "#636766"); } context.fillStyle = gradient; context.fill(); curColor = (curColor === 'white') ? 'black' : 'white'; }
增長鼠標點擊事件,在棋盤上尋找最近的落子點繪製棋子。設置一個當前棋子顏色變量,黑白交替繪製,實現這個功能在棋子繪製函數中就已經存在:curColor = (curColor === 'white') ? 'black' : 'white';下邊是鼠標事件監聽函數算法
//鼠標落下,畫棋子 chessboard.onclick = function(e) { ...... var x = e.offsetX; var y = e.offsetY; var i = Math.floor(x / 40); var j = Math.floor(y / 40); if(chessManStatus[i][j] === 0) { drawChessMan(i, j); ...... } }
咱們不得不認可「計算機」的計算速度比人類要快的多,它可以每秒進行億萬次計算,並且按照既定的流程走,它永遠不會累,也不會失誤。若是我是「計算機」,我就把全部對手可能會贏的狀況全都算出來,而後根據下棋的過程,根據每一次的數據狀況,計算出一個座標,這個座標棋子可以阻止對手贏比賽,也能讓本身更快的贏比賽。事實上,計算機也是這樣作的,提早計算好全部可能會贏的狀況對聰明的人類來講只須要幾秒,可是對於計算機來講,作完這件事情並存儲全部的數據,只是毫秒間的工做而已。下面是程序的實現(和咱們想象的同樣:將橫的、豎的、傾斜的連着五個相同棋子的狀況都考慮進去,就是全部會贏的狀況。並且計算機還將棋子的狀態,本身和人的數據作分類處理。)canvas
var wins = [], personWin = [], computerWin = [],chessManStatus = [], winCount = 0; //全部獲勝的數量和數量統計 for(var i=0;i<15;i++) { wins[i] = []; chessManStatus[i] = []; for(var j=0;j<15;j++) { wins[i][j] = []; chessManStatus[i][j] = 0; } } for(var i=0;i<15;i++) { for(var j=0;j<11;j++) { for(var k=0;k<5;k++){ wins[i][j+k][winCount] = true; } winCount++; } } for(var i=0;i<15;i++) { for(var j=0;j<11;j++) { for(var k=0;k<5;k++){ wins[j+k][i][winCount] = true; } winCount++; } } for(var i=0;i<11;i++) { for(var j=0;j<11;j++) { for(var k=0;k<5;k++){ wins[i+k][j+k][winCount] = true; } winCount++; } } for(var i=0;i<11;i++) { for(var j=14;j>3;j--) { for(var k=0;k<5;k++){ wins[i+k][j-k][winCount] = true; } winCount++; } } for(var i=0; i<winCount; i++) { personWin[i] = 0; computerWin[i] = 0; }
通過經過棋盤上全部可能勝利的狀況不過572種而已!函數
若是我是計算機,接下來我要作的就是當「聰明的人類」下好棋以後,我怎樣下好本身的棋。這個棋的目標:不讓人類贏,讓本身贏!做爲人類的我會大概看一下棋盤上的全部棋子,而後選好一個落棋點,我很羨慕計算機的計算能力,若是我是它我就能夠計算一下棋盤上的每個座標點,對它們的狀況進行評估打分,而後選得分最高的點下棋。固然計算機就是這麼作的,每一次下棋它都作了128700次計算來挑選出最合適的落子點。而進行這麼屢次計算也不過是毫秒之間的事情,因此計算機下棋特別的快,快到「人類」看不出它的反應,幾乎和本身同時下棋的,或許你的棋子剛落下,它就贏了,感受特別沮喪!優化
function computerAI() { //電腦智能下棋 var personScore = [],computerScore = [],maxScore = 0,curX = 0, curY = 0; for(var i=0; i<15; i++) { personScore[i] = [];computerScore[i] = []; for(var j=0; j<15; j++) { personScore[i][j] = 0;computerScore[i][j] = 0; } } for(var i=0; i<15; i++) { for(var j=0; j<15; j++) { if(chessManStatus[i][j] == 0) { for(var k=0; k<winCount; k++) { if(wins[i][j][k]) { if(personWin[k] == 1 || personWin[k] == 2 || personWin[k] == 3 || personWin[k] == 4) { personScore[i][j] += personWin[k]*personWin[k] * 200; } if(computerWin[k] == 1 || computerWin[k] == 2 || computerWin[k] == 3 || computerWin[k] == 4) { computerScore[i][j] += (computerWin[k]*computerWin[k] - 1) * 200 + 399; } } } if(personScore[i][j] > maxScore) { maxScore = personScore[i][j]; curX = i; curY = j; }else if(personScore[i][j] == maxScore) { if(computerScore[i][j] > computerScore[curX][curY]) { curX = i; curY = j; } } if(computerScore[i][j] > maxScore) { maxScore = computerScore[i][j]; curX = i; curY = j; }else if(computerScore[i][j] == maxScore) { if(personScore[i][j] > personScore[curX][curY]) { curX = i; curY = j; } } } } } drawChessMan(curX, curY); ...... }
這個打分算法是整個算法的核心。計算機將連在一塊兒的棋子作打分,1個棋子分數很低,只有200;兩個棋子就會翻到800;三個棋子會更高,冪次運算。有了評分標準之後,計算機開始評分,先給對手評分,給對手評完分數後再給本身評分,計算機本身的評分規則比對手評分規則高,可是原則上是2個棋子連在一塊兒的評分不會高於對手三個棋子連在一塊兒的:計算機在本身贏以前是不會讓對手有贏的機會的。spa
那麼遊戲什麼事件結束呢?固然是有五個棋子連在一塊兒就結束了。這些工做就交給計算機來作吧:計算機記錄每個贏的狀況下連珠棋子的個數,當有棋子達到5時遊戲就結束了,固然黑白棋子是互斥的,當白棋在一個位置有連珠時,黑棋就永遠沒有機會了,咱們在程序上給它設置爲6,無論它怎樣加連珠,都不多是5了。初始化一個gameOver變量爲false,標識遊戲是否結束,當遊戲結束時,取反就行:gameOver = !gameOver了。設計
for(var k=0; k<winCount; k++) { if(wins[curX][curY][k]) { computerWin[k]++; personWin[k] = 6; if(computerWin[k] == 5) { alert('電腦贏了!'); gameOver = !gameOver; } } }
我認可本身代碼寫的很爛,考慮不到不少優化的狀況。可是我把能想到的,能作到的還都是盡力作到了。好比我在一個已經有棋子的地方點擊,是不會讓系統從新繪製棋子的;我在遊戲結束以後,不會讓接下來的工做繼續進行的;沒有從新開始,沒有善後工做,這些就留想要作一個完美做品的人來作吧。寫這個智能算法給個人感悟就是我更可以像計算機同樣的去思考問題了,人類的思考能力會受到情感的左右,而計算機不會,一個越成熟的人,越會像計算機同樣,像程序同樣,作事情井井有理,受情感因素的干擾會很小。3d