電腦遊戲中的人工智慧製作 (轉)

worldblog發表於2007-12-02
電腦遊戲中的人工智慧製作 (轉)[@more@]

 

 遊戲隨著與顯示解析度等大幅提升,以往很多不可能或非常難以實現的電腦遊戲如此都得以順利完成。雖然電腦遊戲的呈現是那麼地多樣化,然而卻與我們今日所要探討的主題,人工智慧幾乎都有著密不可分的關係。
 在角色扮演遊戲中,員與企劃人員需要精確地在電腦上將一個個所謂的“怪物”在戰門過程中栩栩如生地製作出來;所以半獸人受了重傷懂得逃跑,法師懂得施展攻性法術。
 目前能讓人立刻想到與人工智慧有密切關係的遊戲有兩種:
  一是所謂的戰棋/策略模擬遊戲,二則是棋弈遊戲。人工智慧的比重與深淺度,在不同的遊戲型別中各有不一。有的電腦遊戲非標榜著高人工智慧不可,不然沒有人買;有的則是幾乎渺茫到讓玩家無法感覺有任何人工智慧的存在。            

 導向式思考

 AI最容易製作的的方式,同時也是早期遊戲AI發展的主要方向就是規則導向或稱之為假設導向。在一些比較簡單的電腦遊戲中,程式設計師可以好不困難地將遊戲中的規則與設定轉化成一條條的規則,然後將它們寫成電腦程式。讓我們以角色扮演遊戲為例。決大多數的企畫在設定所謂電腦怪物時,所設定的屬性通常有以下幾種:

  生命值 力 力 法力  屬性

 最後一個“屬性”是我在設定時喜歡增加的專案之一。透過這項屬性的設定,我可以把怪物設定成“貪生怕死的”,也可以把戰士設定為“視死如歸”。以目前我們所掌握的資料,在戰門中的大綱如是誕生了:                          

規則一

if (生命值< 10) // 邊臨死亡了嗎 
{  if (屬性== 貪生怕死)               
   結果 = 試圖逃跑               
  if (有任何恢復生命值的物品或法術可用)      
   結果 = 使用或施展相關物品或法術       
}                                                 

規則二
 
if (可施攻擊性法術 && 有足夠法力)
{                        
   結果 = 施展攻攻擊性法術             
}                        

 由以上一連串的“如果--就--”規則設定,建立了最基本的AI。說這樣的制方式只能建立基本AI其實並不當然正確。只要建立足夠及精確的規則,這樣的方式仍然有一定水準的表現。
 規則導向的最大優點就是易學易用。在沒有深奧的理論概念的前提下,仍有廣大的使用群。所以很多老道的玩家常常沒兩下就摸清楚敵人的攻擊策略,移動方式等等。

 推論式思考

 相信曾經接觸過電腦語言課程,或是自習過相關書籍的朋友們,都曾曾經聽過一個著名的程式,那就是井字遊戲。用井字遊戲作為討論AI的教材,我個人覺得是最適當的例子。或許有人還不知道井字遊戲怎麼玩。只要任何一方在三乘三的方格中先先成一線便勝利了。我們在前面談過的規則導向,在這裡也可以派得上用場。

 if任何一線已有我方兩子&&另外一格仍空//我方即將成一線嗎
  結果 = 該空格                     
 if任何一線已有敵方兩子&&另外一格仍空//防止敵方作成一線 
  結果 = 該空格                     
 if任何一線已有我方一子&&另外兩格仍空//作成兩子    
  結果 = 該空格

 有一次我在某本電腦書上,同樣地也看到某些以井字遊戲為介紹的範例。不同的是,我幾乎看不到任何規則導向的影子。但在仔細分析該程式碼後,我得到了極大的啟發,原來AI是可以不用這麼多規則來製作的。它用的方法正是在電腦AI課程中重要的概念:極大極小法。我在這裡只說明這法則的概念。繼續以井字遊戲為例,電腦先在某處下子,接著會以假設的方式,替對方下子,當然,必須假設對方下的是最佳位置,否則一切則毫無意義。在假設對方下子的過程中,自然又需要假設我方的下一步回應,如此一來一往,直到下完整局遊戲為止。

底下是節錄書中的程式片段:                       
 
bestMove(int p, int*v)
{   int i; 
   int lastTie;                  
   int lastMove;                 
   int subV;                                   
/*First, check for a tie*/            
    if (isTie()) {              
     *v=0;               
     return(0);              
   };
/*If not a tie, try each potential move*/
 for (*v=-1, lastTie=lastMove=-1,i=0;i<9;i++)
  {
   /*If this isn't a possible, skit*/          
   if (board[i]!=0) continue;
   /* Make the move. */
    lastMove=i; 
    board[i]=p;                             
   /* Did it win? */                       
    if (hasWon(p)) *v=1;                     
    else{                             
   /*If not, find out how good the other s can do*/
     bestMove(-p,&subV);                      
   /* If they can only lose, this is still a win.*/
      if (subV==-1) *v=1;       
   /* Or, if it's a tie, remember it. */         
       else if (subV==0){                 
          *v=0;       
          lastTie=i; 
          };                          
       };                              
/* Take back the move. */           
           board[i]=0;          
/*If we found a win, return immediately
     (can't do any better than that)*/     
  if (*v==1) return(i);                     
/*If we didn't find any wins, return a tmove.*/         
  if (*v==0) return(lastTie);                      
/*If there weren't even any ties, return a loosing move.*/     
  else return(lastMove); 
};    

 國外的一些論壇曾舉行過256位元組的遊戲設計比賽。作品非常多,其中有一件作品正巧也是井字遊戲。作者用區區兩百多行就寫了與上述程式演算方式完全相同的作品,可見功力確實了的。另外,我也很希望類似的活動能在國內推展起來。對了,在這樣的比賽條件限制下,除了語言外,幾乎沒有其它的選擇了。    

  .386c                        
  code      segment byte public use16      
          assume cs:code, ds:code      
                            
          org   100h            
                            
  tictac     proc  far             
                            
  start:                       
          push  cs             
          pop   ds             
          mov   ax,0B800h     ; 清除螢幕
          mov   es,ax       ;    
          xor   di,di       ;    
          mov   cx,7D0h      ;    
          mov   ax,0F20h      ;    
          rep   stosw       ;    
          xor   cx,cx       ;    
          mov   dl,5            
  loc_1:                       
          call  printBoard         
  loc_2:                       
          mov   ah,8        ; 等待按鍵
          int   21h             
                            
          movzx  bx,al            

          sub   bl,31h       ; 如果不是1..9
          jc   loc_2       ; 則重新輸入 
          cmp   bl,8              
          ja   loc_2              
          cmp   data_1[bx],al          
          jne   loc_2              
          mov   byte ptr data_1[bx],'x'     
          dec   dl               
          jz   short loc_3           
          mov   al,'o'             
          call  bestMove            
          mov   [si],al             
          call  isWin   ; 判斷是否已取得勝利 
          jnc   loc_1              
  loc_3:                          
          call  printBoard           
          mov   ax,4C00h            
          int   21h               
                              
  data_1        '12'              
  data_2     db   '3456789'            
  data_3     db   0                
                              
  tictac     endp                  
                              
                              
  printBoard   proc  near              
          mov   si,offset data_1        
          mov   di,548h             
          mov   cl,3              
                              
  locl_4:                       
          movsb                  
          add   di,5              
          movsb                  
          add   di,5              
          movsb                  
          add   di,133h             
          loop  locloop_4            
                              
          retn                  
  printBoard   endp                  
                              
                              
  isWin      proc  near              
          mov   bx,1              
          mov   bp,3              
          call  sub_3    ; 檢查橫向是否完成 
          inc   bx               
          inc   bx               
          dec   bp               
          dec   bp               
          call  sub_3    ; 檢查縱向是否完成 
          call  sub_4    ; 檢查斜向是否完成
          clc
          retn                  
  isWin      endp                  
                              
  loc_5:                         
          stc                   
          retn                  
                                                            
  sub_3      proc  near              
          mov   ah,3              
          mov   si,offset data_1        
  loc_6:                         
          mov   di,si              
          call  sub_5              
          add   si,bp             
          dec   ah               
          jnz   loc_6              
          retn                  
  sub_3      endp                  
                             
  sub_4      proc  near              
          mov   di,offset data_1       
          inc   bx              
          call  sub_5             
          mov   di,offset data_2        
          dec   bx               
          dec   bx               
          call  sub_5              
          retn                  
  sub_4      endp                  
                              
                              
  sub_5      proc  near              
          mov   cl,3              
                              
  locloop_7:                       
          cmp   [di],al             
          jne   short loc_ret_8         
          add   di,bx              
          loop  locloop_7            
                              
          add   sp,4              
          jmp   short loc_5           
                              
  loc_ret_8:                       
          retn                      
  sub_5      endp                      
                                  
  bestMove    proc  near                  
          mov   bx,31FEh                
          mov   cl,9                  
          mov   di,offset data_1            
                                  
  locloop_9:                           
          cmp   [di],bh     ; #empty?        
          jne   short loc_12  ; #no, skip       
          mov   [di],al                 
          pusha                      
          call  isWin      ; #CY: Win       
          popa          ;            
          jnc   short loc_10  ;            
          mov   bl,1                  
          mov   si,di                  
          mov   [di],bh                 
          retn                      
  loc_10:                             
          pusha                      
          xor   al,17h ; good! toggle 'o' / 'x'
          call  bestMove                
          mov   data_3,bl                
          popa                      
          mov   ah,data_3                
          neg   ah                   
          cmp   ah,bl                  
          jle   short loc_11              
          mov   bl,ah                  
          mov   si,di                  
  loc_11:                             
          mov   [di],bh                 
  loc_12:                             
          inc   bh                   
          inc   di                   
          loop  locloop_9                
                                  
          cmp   bl,0FEh                 
          jne   short loc_ret_13            
          xor   bl,bl                  
                                  
  loc_ret_13:                           
          retn                      
  bestMove    endp                      
  code      ends

          end   start                  


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-987295/,如需轉載,請註明出處,否則將追究法律責任。

相關文章