一個JavaScript寫的黑白棋AI

來源:互聯網
上載者:User

賴勇浩(http://laiyonghao.com)

首先,這個代碼不是我寫的,但注釋是我加上去的。作者是shaofei cheng,他的網站:http://shaofei.name

第二,目前這個代碼只是使用了 alpha-beta 剪枝,棋力還弱,有很大的最佳化空間。但是代碼寫得非常清晰,如果有朋友對人機弈棋方面的課題有興趣又還沒有入門,這份代碼作為一個例子是很棒的。

第三,目前電腦只能搜尋 3 層,我覺得加上迭代深化和曆史啟發演算法之後,搜尋到 5 層是不成問題的。現代 JavaScript 的效能不錯。

第四,作者在代碼裡展示了不少技巧,值得學習和借鑒,哪怕不懂 JavaScript 也很容易看懂代碼(我也不懂)。

第五,試試這個 AI 的棋力:http://shaofei.name/OthelloAI/othello.html

以下是代碼:

var AI = {};<br />new function(){<br />AI.Pattern= pattern;<br />// 定義了 8 個位移量<br />// 可以簡單通過加法得到任一點周圍 8 個點的座標<br />// -11 -10 -9<br />// -1 x 1<br />// 9 10 11<br />// 如左上方的座標為 x + (-11)<br />var directions=[-11,-10,-9,-1,1,9,10,11];<br />function pattern()<br />{<br />// 把整個棋盤填滿 0<br />for(var i=0;i<100;i++)this[i]=0;<br />// 中間的 4 個格子,先放上兩黑兩白的棋子<br />this[54]=this[45]=1;this[55]=this[44]=2;<br />// 黑淨勝外圍子數目(黑減去白),估值時用。<br />this.divergence=0;<br />// 當前可走棋方為黑棋<br />this.color=1;<br />// 已經走了幾步棋<br />this.moves=0;<br />// 穩定原型<br />// 0 是空白,1 是黑棋,2 是白棋,3 是邊界<br />// 把 8 * 8 的棋盤擴充成 10 * 10,是一種技巧<br />// 可以簡化座標有效性的判斷<br />var stableProto = [<br />3, 3, 3, 3, 3, 3, 3, 3, 3, 3,<br />3, 0, 0, 0, 0, 0, 0, 0, 0, 3,<br />3, 0, 0, 0, 0, 0, 0, 0, 0, 3,<br />3, 0, 0, 0, 0, 0, 0, 0, 0, 3,<br />3, 0, 0, 0, 0, 0, 0, 0, 0, 3,<br />3, 0, 0, 0, 0, 0, 0, 0, 0, 3,<br />3, 0, 0, 0, 0, 0, 0, 0, 0, 3,<br />3, 0, 0, 0, 0, 0, 0, 0, 0, 3,<br />3, 0, 0, 0, 0, 0, 0, 0, 0, 3,<br />3, 3, 3, 3, 3, 3, 3, 3, 3, 3<br />]<br />// 從一個 8 * 8 的棋盤載入狀態<br />this.load=function(arr)<br />{<br />for(var y=1;y<=8;y++)<br />{<br />for(var x=1;x<=8;x++)<br />{<br />this[y*10+x]=arr[y-1][x-1];<br />}<br />}<br />}<br />// 判斷能不能 pass<br />// 如果能,則當前可走棋方變更<br />this.pass=function()<br />{<br />for(var y=1;y<=8;y++)<br />{<br />for(var x=1;x<=8;x++)<br />{<br />if(this[y*10+x]==0)<br />{<br />// 有任何一步棋可走,都不可以 Pass<br />if(this.move(x,y,this.color))<br />{<br />return false;<br />}<br />}<br />}<br />}<br />//alert("pass");<br />// 這是一個技巧,因為 this.color 的範圍是 {1, 2}<br />// 所以當 color 是 1 時,執行完下一語句後就是 2<br />// 當 color 是 2 時,執行完下一語句後就是 1<br />this.color = 3 - this.color;<br />return true;<br />}<br />this.clone=function()<br />{<br />function pattern(){}<br />pattern.prototype=this;<br />return new pattern();<br />}<br />this.toString=function()<br />{<br />var icon=[" ","*","o"]<br />var r="";<br />for(var y=1;y<=8;y++)<br />{<br />for(var x=1;x<=8;x++)<br />{<br />r+=icon[this[y*10+x]]+" ";<br />//r+=stableDiscs[y*10+x]+" ";<br />}<br />r+="/n";<br />}<br />return r+this.exact();<br />}</p><p>// 淨勝子數<br />this.exact=function()<br />{<br />// 這裡是一個技巧, r[0] 是不使用的,r[1] r[2] 對應黑白棋子的個數<br />var r=[0,0,0];<br />for(var y=1;y<=8;y++)<br />{<br />for(var x=1;x<=8;x++)<br />{<br />r[this[y*10+x]]++; // 數目加一<br />}<br />}<br />// 使用中色彩的數量為 0,輸了,返回負極值<br />if(r[this.color]==0) return -64;<br />// 敵對顏色的數量為 0,贏了,返回極值<br />if(r[3-this.color]==0) return 64;<br />// 返回當前走棋方比對方多的數量<br />return r[this.color]-r[3-this.color];<br />}<br />// 對棋盤的估值<br />this.calculate=function()<br />{<br />// 基本估值方法:<br />// 1、能占棋盤四角是很有價值的<br />// 2、鄰近棋盤四角的位子是很差的<br />// 3、穩定子<br />// 4、外圍子淨勝數<br />var r=[0,0,0];<br />var r=this.divergence;<br />// 如果左上方有棋子,自己的,就+30分,敵方的,-30 分<br />if(this[11])r+=((this[11]==this.color)?1:-1)*30;<br /> // 次左上方,分值是 -15<br />else if(this[22]==this.color)r-=15;<br />// 右上方,分值 30<br />if(this[18])r+=((this[18]==this.color)?1:-1)*30;<br />// 次右上方,分值 -15<br />else if(this[27]==this.color)r-=15;<br />// 左下角,分值 30<br />if(this[81])r+=((this[81]==this.color)?1:-1)*30;<br />// 次左下角,分值 -15<br />else if(this[72]==this.color)r-=15;<br />// 右下角,分值 30<br />if(this[88]){r+=((this[88]==this.color)?1:-1)*30;}<br />// 次右下角,分值 -15<br />else if(this[77]==this.color)r-=15;<br />// 尋找穩定子,<br />// 穩定子就是挨著 4 個角點並且周邊的棋子要麼是同色,要麼是邊界<br />//var color = this.color;<br />var stableDiscs=stableProto.slice();</p><p>var queue = [];<br />if(this[11]!=0) queue.push([11,this[11]]);<br />if(this[18]!=0) queue.push([18,this[18]]);<br />if(this[81]!=0) queue.push([81,this[81]]);<br />if(this[88]!=0) queue.push([88,this[88]]);<br />while(queue.length)<br />{<br />var position = queue[0][0];<br />var c = queue[0][1];<br />// 不懂 JS 的數組的記憶體管理演算法,不過感覺從頭上刪除肯定是比較慢的,<br />// 我感覺從後面刪除會更好,或者使用標記不刪除的方法效能會更好<br />queue.shift();<br />//if(stableDiscs[position]==0 || stableDiscs[position]==3) continue;<br />stableDiscs[position] = c;<br />if( (stableDiscs[position-10]==3 || stableDiscs[position+10]==3 || stableDiscs[position-10] == c || stableDiscs[position+10] == c) &&<br />(stableDiscs[position-1]==3 || stableDiscs[position+1]==3 || stableDiscs[position-1] == c || stableDiscs[position+1] == c) &&<br />(stableDiscs[position-11]==3 || stableDiscs[position+11]==3 || stableDiscs[position-11] == c || stableDiscs[position+11] == c) &&<br />(stableDiscs[position-9]==3 || stableDiscs[position+9]==3 || stableDiscs[position-9] == c || stableDiscs[position+9] == c) )<br />{<br />stableDiscs[position]=c;<br />// 穩定子的分值為 7<br />r += ((c==this.color)?1:-1)*7;<br />// 進一步擴充,尋找穩定子<br />for(var i = 0;i <directions.length ; i++)<br />if(stableDiscs[directions[i]+position]==0 && this[directions[i]+position]==c)<br />queue.push([directions[i]+position,c]);<br />}<br />}</p><p>// 返回估值<br />return r;<br />}<br />this.toLocalString=function(depth)<br />{<br />var r="";<br />if(!depth)depth=0;</p><p>for(var y=1;y<=8;y++)<br />{<br />for(var x=1;x<=8;x++)<br />{<br />if(this[y*10+x]!=0)r+=(this[y*10+x]==1?"*":"o")+" ";<br />else<br />{<br />var tmp=this.move(x,y,this.color);<br />if(tmp)<br />{<br />var tmp2=-tmp.search(-Infinity,Infinity,depth);<br />if(tmp2<0||tmp2>9)r+=tmp2;<br />else r+=" "+tmp2;<br />}<br />else r+="X ";<br />}<br />}<br />r+="/n";<br />}<br />return r+this.exact();<br />}<br />// 電腦去找一步可走的棋步<br />// 這裡 AI 部分的入口<br />this.computer=function(depth,exactDepth){<br />if(!depth)depth=0;<br />if(!exactDepth)exactDepth=depth;<br />var r=[];<br />var max=-Infinity;<br />for(var y=1;y<=8;y++)<br />{<br />for(var x=1;x<=8;x++)<br />{<br />if(this[y*10+x])continue;<br />// 找到一個空白欄框子<br />else<br />{<br />// 嘗試走這個格子<br />var tmp=this.move(x,y,this.color);<br />// 不成功,非法<br />if(!tmp)continue;<br />// 已走步數+已搜尋深度 >= 有 60 步<br />// 這時使用精確搜尋得到更精確的結果<br />if(this.moves+exactDepth>=60)<br />{<br />var v=-tmp.exactSearch(-Infinity,Infinity);<br />//alert([x,y]+":"+v);<br />}<br />// 離四個角最近的那 3 * 4 個格子,則多搜尋一層<br />// 因為對手可能在下一手下在角上,會出現大翻盤。<br />else if( (x==2||x==7) && (y==2||y==7) )<br />var v=-tmp.search(-Infinity,Infinity,depth+1);<br />else<br />var v=-tmp.search(-Infinity,Infinity,depth);<br />// 還不如之前的棋步<br />if(v<max)continue;<br />// 比之前的棋步好<br />if(v>max){<br />// 儲存起來<br />r=[[x,y]];max=v;<br />}<br />// 另一個可選的棋步<br />else r.push([x,y]);<br />}<br />}<br />}<br />// 在所有可選的棋步中,隨機播放一個,讓玩家覺得比較多變化,不那麼單調。<br />var tmp=Math.floor(Math.random()*r.length);<br />return r[tmp];<br />}<br />// 搜尋演算法<br />// 使用負極大值形式的 Alpha-Beta 剪枝搜尋演算法<br />this.search=function(alpha,beta,depth,pass){<br />// 葉子節點,返回估值<br />if(depth==0)return this.calculate();<br />var canmove=false;<br />for(var y=1;y<=8;y++)<br />{<br />for(var x=1;x<=8;x++)<br />{<br />if(this[y*10+x]!=0)r+=(this[y*10+x]==1?"*":"o")+" ";<br />else<br />{<br />var tmp=this.move(x,y,this.color);<br />if(!tmp)continue;<br />canmove=true;<br />// 往更深搜尋<br />var r=-tmp.search(-beta,-alpha,depth-1);<br />//if(depth==4)WScript.echo(r);<br />// 收窗視窗<br />if(r>=alpha)alpha=r;<br />// 勝著<br />if(alpha>beta)return Infinity;<br />}<br />}<br />}<br />// 返回當前局面的最佳著法估值<br />if(canmove)return alpha;<br />// 雙方都沒有可下子之處,返回淨勝子數<br /> if(pass) return this.exact();<br />// pass 一次,往深搜尋<br />this.color=3-this.color;<br />return -this.search(-beta,-alpha,depth-1,true);<br />}<br />// 精確搜尋,這段的演算法原理跟 search 是一樣的<br />this.exactSearch=function(alpha,beta,pass){<br />// 已經走了 60 步了,返回淨勝子數<br />if(this.moves==60)return this.exact();<br />var canmove=false;<br />for(var y=1;y<=8;y++)<br />{<br />for(var x=1;x<=8;x++)<br />{<br />if(this[y*10+x]!=0);//r+=(this[y*10+x]==1?"*":"o")+" ";<br />else<br />{<br />var tmp=this.move(x,y,this.color);<br />if(!tmp)continue;<br />canmove=true;<br />var r=-tmp.exactSearch(-beta,-alpha);<br />if(r>=alpha)alpha=r;<br />if(alpha>beta)return Infinity;<br />}<br />}<br />}<br />if(canmove)return alpha;<br />if(pass)return this.exact();<br />this.color=3-this.color;<br />return -this.exactSearch(-beta,-alpha,true);<br />}<br />// 嘗試在 x, y 放下 this.color 顏色的棋子,成功返回下一棋盤狀態,否則返回 null<br />this.move=function(x,y)<br />{<br />// 複製目前狀態<br />var pattern=this.clone();<br />pattern.color=3-this.color;<br />// 注意這個負號<br />pattern.divergence=-pattern.divergence;<br />// move 數++<br />pattern.moves++;<br />var canmove;<br />canmove=false;<br />// 放在函數入口處,可以最佳化效能<br />// 把 10*y+x 放入臨時變數可最佳化效能<br />if(pattern[10*y+x]!=0)return null;<br />// 8 方向判斷<br />for(var i=0;i<8;i++)<br />{<br />// 轉換為一維索引<br />var p=10*y+x+directions[i];<br />// 鄰近的格子上棋子不同色<br />if(pattern[p]==3-this.color)<br />while(pattern[p]!=0)<br />{<br />// 往同方向搜尋<br />p+=directions[i];<br /> // 另一端還有一個自己的棋子,則是一個可走的點。<br />if(pattern[p]==this.color)<br />{<br />canmove=true;<br />// 把中間的棋子翻過來<br />while((p+=-directions[i])!=10*y+x)<br />{<br />pattern[p]=this.color;<br />//alert(p);<br />for(var d=0;d<8;d++)<br />{<br />// 非空<br />if(!pattern[p+directions[d]]<br />// 非邊界<br />&&p+directions[d]>10<br />&&p+directions[d]<89<br />&&(p+directions[d])%10!=0<br />&&(p+directions[d])!=9)<br />// 外圍淨勝子數增加<br />pattern.divergence++;<br />}<br />}<br />break;<br />}<br />}<br />}<br />// 返回新的棋盤狀態<br />if(canmove){pattern[10*y+x]=this.color;return pattern;}<br />else return null;<br />}<br />}<br />//pattern.prototype = emptyboard;<br />//WScript.echo(new pattern().move(5,6).move(6,4).move(4,3).move(3,4).toLocalString(3));<br />//WScript.echo(new pattern().move(5,6).search(-Infinity,Infinity,2));<br />}()<br />

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.