MATLAB—一字棋(極大極小搜尋)
阿新 • • 發佈:2019-01-11
init.m
%初始化棋盤狀態
function cur=init()
cur=rand(3,3); %儲存當前棋盤的狀態
%計算機為先手時的初值,即均為0
for i=1:3
for j=1:3
cur(i,j)=0;
end
end
end
checkWin.m
%檢查是否有一方贏棋(0:沒有任何一方贏;1:計算機贏;-1:人贏) function flag = checkWin(cur) %該方法沒有判斷平局 for i=1:3 %判斷一行 if cur(i,1)==1&&cur(i,2)==1&&cur(i,3)==1 %是否計算機贏 flag=1; return; end if cur(i,1)==-1&&cur(i,2)==-1&&cur(i,3)==-1 %是否人贏 flag=-1; return; end %判斷一列 if cur(1,i)==1&&cur(2,i)==1&&cur(3,i)==1 %是否計算機贏 flag=1; return; end if cur(1,i)==-1&&cur(2,i)==-1&&cur(3,i)==-1 %是否人贏 flag=-1; return; end end %判斷兩個對角線 if (cur(1,1)==1&&cur(2,2)==1&&cur(3,3)==1)||(cur(3,1)==1&&cur(2,2)==1&&cur(1,3)==1) %是否計算機贏 flag=1; return; end if((cur(1,1)==-1&&cur(2,2)==-1&&cur(3,3)==-1)||(cur(3,1)==-1&&cur(2,2)==-1&&cur(1,3)==-1))%是否人贏 flag=-1; return; end %沒有任何一方贏 flag=0; return; end
value.m
%評估當前棋盤狀態的值(同時可以用p或q判斷是否平局) function pq= value(cur) p=0; q=0; isWin=checkWin(cur); tmpQP=rand(3,3); %表示棋盤資料的臨時陣列,其中的元素0表示該格為空 if(isWin==-1) %如果使用者玩家贏了,置棋盤估計值為負無窮 pq=-10000; return; end if(isWin==1) %如果計算機贏了,置棋盤估計值為無窮 pq=10000; return; end %計算機一方(MAX) for i=1:3 %將棋盤中的空格填滿自己的棋子,既將棋盤陣列中的0變為1 for j=1:3 if cur(i,j)==0 tmpQP(i,j)=1; else tmpQP(i,j)=cur(i,j); end end end for i=1:3 %計算共有多少連成3個1的行 if tmpQP(i,1)+tmpQP(i,2)+tmpQP(i,3)==3 p=p+1; end end for i=1:3 %計算共有多少連成3個1的列 if tmpQP(1,i)+tmpQP(2,i)+tmpQP(3,i)==3 p=p+1; end end if tmpQP(1,1)+tmpQP(2,2)+tmpQP(3,3)==3 p=p+1; %計算共有多少連成3個1的對角線 end if tmpQP(3,1)+tmpQP(2,2)+tmpQP(1,3)==3 p=p+1; end %人一方(MIN) for i=1:3 %將棋盤中的空格填滿自己的棋子,既將棋盤陣列中的0變為-1 for j=1:3 if cur(i,j)==0 tmpQP(i,j)=-1; else tmpQP(i,j)=cur(i,j); end end end for i=1:3 %計算共有多少連成3個-1的行 if tmpQP(i,1)+tmpQP(i,2)+tmpQP(i,3)==-3 q=q+1; end end for i=1:3 %計算共有多少連成3個-1的列 if tmpQP(1,i)+tmpQP(2,i)+tmpQP(3,i)==-3 q=q+1; end end if tmpQP(1,1)+tmpQP(2,2)+tmpQP(3,3)==-3 q=q+1; %計算共有多少連成3個-1的對角線 end if tmpQP(3,1)+tmpQP(2,2)+tmpQP(1,3)==-3 q=q+1; end %返回評估出的棋盤狀態的值 pq=p-q; end
tuozhan.m
function [tail,open] = tuozhan(g,tail,open,index) cur=open{1,index}.S; k=0; %對當前狀態進行拓展 for i=1:3 for j=1:3 flag=1; %標誌拓展後的結點是否與之前拓展的結點同構 if cur(i,j)==0 k=k+1; tmpcur=cur; tail=tail+1; %判斷當前結點是MAX還是MIN走的 if g/2~=1 %根據拓展深度的不同,值有所變化,此程式只試探兩步 tmpcur(i,j)=1; %若是MIN走的,下一步MAX走 else tmpcur(i,j)=-1; %若是MAX走的,下一步MIN走 end %判斷拓展後的結點是否和之前的結點同構,若同構flag=0,否則flag=1 for k=index:tail-1 if open{1,k}.S==rot90(tmpcur,1) flag=0; tail=tail-1; break; end if open{1,k}.S==rot90(tmpcur,2) flag=0; tail=tail-1; break; end if open{1,k}.S==rot90(tmpcur,3) flag=0; tail=tail-1; break; end if open{1,k}.S==fliplr(tmpcur) flag=0; tail=tail-1; break; end if open{1,k}.S==flipud(tmpcur) flag=0; tail=tail-1; break; end if open{1,k}.S==rot90(flipud(tmpcur),1) flag=0; tail=tail-1; break; end if open{1,k}.S==rot90(flipud(tmpcur),2) flag=0; tail=tail-1; break; end if open{1,k}.S==rot90(flipud(tmpcur),3) flag=0; tail=tail-1; break; end if open{1,k}.S==rot90(fliplr(tmpcur),1) flag=0; tail=tail-1; break; end if open{1,k}.S==rot90(fliplr(tmpcur),2) flag=0; tail=tail-1; break; end if open{1,k}.S==rot90(fliplr(tmpcur),3) flag=0; tail=tail-1; break; end end %如果flag不為0,說明沒有同構,可以拓展,加入open表裡 if flag~=0 open{1,tail}.g=g; open{1,tail}.v=value(tmpcur); open{1,tail}.S=tmpcur; open{1,tail}.fa=cur; end end end end end
chushi.m
function table=chushi(cur)
table=cell(1);
table{1,1}.g=0;
table{1,1}.v=value(cur);
table{1,1}.S=cur; %當前節點的狀態
table{1,1}.fa=[]; %父節點的狀態,這樣的保留可以從找到的目標狀態追蹤到初始狀態
yunxing.m
%第一階段 A=[0 0 0;0 0 0;0 0 0]
%第二階段 A=[0 -1 0;0 1 0;0 0 0]
%第三階段 A=[0 -1 -1;0 1 0;1 0 0]
function jieguo = yunxing(cur)
close=cell(1);
open=chushi(cur);
index=1; %記錄拓展結點的下標,為tuozhan.m方便
tail=1;
g=1;
c=1;
while 1
if g>2 %若深度大於2,則停止
break;
end
[tail,open]=tuozhan(g,tail,open,index);
close{1,c}=open{1,index}; %將已擴充套件的節點放入CLOSED8表中
c=c+1;
index=index+1;
if index<=tail
g=open{1,index}.g;
g=g+1;
else %當只剩程式方最後一步時
break;
end
end
%端結點靜態估值和倒推值計算
i=c-1; %查close表
while i>0
max=-1000000;
min=1000000;
j=tail; %查open表
while j>1
if open{1,j}.fa==close{1,i}.S
if close{1,i}.g==1 %因只拓展兩層,所以可以直接寫1和0,若拓展深度大於2,則做出相應的改變
if open{1,j}.v<min
min=open{1,j}.v;
end
end
if close{1,i}.g==0
if open{1,j}.v>max
max=open{1,j}.v;
end
end
end
j=j-1;
end
for k=1:tail %將改過的v寫回open表
if open{1,k}.S==close{1,i}.S
if close{1,i}.g==1
open{1,k}.v=min;
end
if close{1,i}.g==0
open{1,k}.v=max;
end
end
end
if close{1,i}.g==1
close{1,i}.v=min;%更新close表中的值
end
if close{1,i}.g==0
close{1,i}.v=max;%更新close表中的值
end
i=i-1;
end
%輸出結果
for i=1:tail
if open{1,i}.g==1
if open{1,i}.v==open{1,1}.v
jieguo=open{1,i}.S;
disp('程式方:');
disp(open{1,i}.S); %查詢open表,看取的哪一個MIN中的MAX值,然後輸出此矩陣
break;
end
end
end
end
operation.m
function []=operation()
input('*****程式方先手*****');
step=0; %記錄走的步數,以判斷是否最後的平局
cur=init(); %當程式方為先手時
while 1
jieguo=yunxing(cur);
step=step+1;
if checkWin(jieguo)==1
disp('計算機贏,您輸了!');
break;
end
if checkWin(jieguo)==-1
disp('恭喜,您贏了!');
break;
end
if checkWin(jieguo)==0
if step==9
disp('平局');
break;
end
end
disp('請輸入位置:');
matri = input('[a b] = ');
a = matri(1); %橫座標
b = matri(2); %縱座標
while jieguo(a,b)~=0
disp('輸入有誤!');
disp('請重新輸入位置:');
matri = input('[a b] = ');
a = matri(1); %橫座標
b = matri(2); %縱座標
end
jieguo(a,b)=-1;
disp('我方:');
disp(jieguo);
step=step+1;
cur=jieguo;
end
end
執行結果: