撰寫簡單的井字遊戲


阿瑟 發表



這篇文章會講解如何利用一些Javascript中的文件物件操作和邏輯判斷來撰寫一個簡易的井字遊戲.

雖然現在用網頁 Script 語言去寫小遊戲已經有點落伍了 (現在多半的網頁小遊戲都是用 Java 和 Flash 完成的), 但其簡單的文件物件 (Document Object) 結構 與 事件處理 (Event Handling), 拿來作為程式設計入門或是促進邏輯設計的思維都是不錯的選擇.

井字遊戲大家應該都有玩過吧? 只要將三個自己的符號 (圈圈或是叉叉) 連成一條線就可以了, 規則非常的簡單.

井字遊戲的完成範例

做跟玩井字遊戲都是一樣的, 一開始當然要先畫出一個大字.

小弟是選擇使用表格, 畫出九個方塊, 每個方塊都是一個物件, 一開始每個方塊都是空的 (含有一個空白字元):

<table border=1 style="border-collapse:collapse;width:150px;height:150px" bordercolor="#000000">

  <tr>
    <td width=50 align="center" id="ttt00">&nbsp;</td>
    <td width=50 align="center" id="ttt01">&nbsp;</td>
    <td width=50 align="center" id="ttt02">&nbsp;</td>
  </tr>

  <tr>
    <td width=50 align="center" id="ttt10">&nbsp;</td>
    <td width=510 align="center" id="ttt11">&nbsp;</td>
    <td width=50 align="center" id="ttt12">&nbsp;</td>
  </tr>

  <tr>
    <td width=50 align="center" id="ttt20">&nbsp;</td>
    <td width=50 align="center" id="ttt21">&nbsp;</td>
    <td width=50 align="center" id="ttt22">&nbsp;</td>
  </tr>

</table>


以上的HTML輸出的是一個 150 * 150 像素的表格. 如下:

     
     
     


每個方塊都有一個代號 (id). 第一列第一個命名為 ttt00, 第一列第二個命名為 ttt01, 第二列第一個命名為 ttt10, 依次類推.

接下來就要開始寫一些 Script.

先寫一個可以把圈和叉畫在方塊內的函式:


var turn = 0;

function playRound(objDest)
{
  if(turn == 0)
  {
    objDest.innerText = "O";
    turn = 1;
  }
  else
  {
    objDest.innerText = "X";
    turn = 0;
  }
}




由於井字遊戲是兩個人輪流玩的, 玩家1玩的時候會畫一個圈, 玩家2玩的時候會畫一個叉, 小弟用了一個整數變數 (此例中的turn) 來決定要畫圈還是畫叉. 這個 playRound() 函式接受一個物件參數, 如果函式呼叫的時候 turn 變數是 0, 則將物件中的內容從空白改為一個圈; 反之, 如果 turn 變數是1, 則將物件的內容從空白改為一個叉. 每當一個圈或是叉被加入方塊, turn就會替換 (從0換成1或是從1換成0), 如此兩個使用者就可以輪流玩.

接著我們要把這個函式應用到剛剛的物件上:

<table border=1 style="border-collapse:collapse;width:150px;height:150px" bordercolor="#000000">
  <tr>
    <td width=50 align="center" id="ttt00" onClick="playRound(this);">&nbsp;</td>
    <td width=50 align="center" id="ttt01" onClick="playRound(this);">&nbsp;</td>
    <td width=50 align="center" id="ttt02" onClick="playRound(this);">&nbsp;</td>
  </tr>

  <tr>
    <td width=50 align="center" id="ttt10" onClick="playRound(this);">&nbsp;</td>
    <td width=50 align="center" id="ttt11" onClick="playRound(this);">&nbsp;</td>
    <td width=50 align="center" id="ttt12" onClick="playRound(this);">&nbsp;</td>
  </tr>

  <tr>
    <td width=50 align="center" id="ttt20" onClick="playRound(this);">&nbsp;</td>
    <td width=50 align="center" id="ttt21" onClick="playRound(this);">&nbsp;</td>
    <td width=50 align="center" id="ttt22" onClick="playRound(this);">&nbsp;</td>
  </tr>

</table>


在剛剛的方塊中加上了事件處理: 當使用者點選一個方塊後, 就會執行 playRound() 函式並且把被點選的方塊當作參數傳遞 (即 this 關鍵字), 這樣就完成了基本繪圖函式.

這樣其實井字遊戲可以算是完成了, 但是先別急著把網頁關掉唷! 試想, 如果有使用者在已經有圈圈叉叉的方塊上點了一下, 那符號不就被覆蓋過了, 整個遊戲就亂掉了.

我們必須修改一下 playRound() 函式, 來更正這個錯誤 (當然, 這就是 bug 囉):

function playRound(objDest)
{
  if(objDest.innerText == " " && turn == 0)
  {  
    objDest.innerText = "O";
    turn = 1;
  }
  else if(objDest.innerText == " " && turn == 1)
  {
    objDest.innerText = "X";
    turn = 0;
  }
  else
  {
    confirm("方塊已經被佔用了!");
  }

}


在這邊我們將原本的playRound()函式改成一定要確定該方塊是空的 (一個空白字元) 才能夠畫上符號, 否則會跳出警告視窗, 告訴玩家他點選的方塊已經被佔用了.

這樣一來就不會錯亂了. 接著要給井字遊戲加上勝負機制. 這樣玩起來才有意思嘛!

這時候就要腦力激盪一下, 仔細想想井字遊戲的規則...

  • 只要三個同樣的符號連成一條線就贏了! 可以怎麼連呢?
  • 一列同樣的符號 (橫)
  • 一欄同樣的符號 (豎)
  • 對角線都是同樣的符號
  • 如果方塊全部滿了, 卻沒人贏, 就平手了
  • 如果要再玩一場, 要畫一個新的井字 - 我們只要刷新方塊就可以了!
  • 我們可以再一次修改 playRound() 函式, 讓他加入其他的功能:

    function playRound(objDest)
    {
      if(objDest.innerText == " " && turn == 0)
      {  
        objDest.innerText = "O";
        turn = 1;
      }
      else if(objDest.innerText == " " && turn == 1)
      {
        objDest.innerText = "X";
        turn = 0;
      }
      else
      {
        confirm("方塊已經被佔用了!");
      }
    
      if(checkRow())
      {
        if(turn == 0)
        {
          if(confirm("玩家2贏了!\n\n要再玩一場嗎?"))
            clearTable();
    	  }
        else if(turn == 1)
        {
          if(confirm("玩家1贏了!\n\n要再玩一場嗎?"))
            clearTable();
        }
      }
      else if(tableIsFull())
      {
        if(confirm("平手\n\n要再玩一場嗎?"))
          clearTable();
      }
    }
    


    這邊我們要將 playRound() 函式下面再加上一個 if/else 判別.

    假設我們會再增加三個函式: checkRow() 來確定是否有一方已經獲得勝利, tableIsFull() 函式來確定是否所有方塊都被填滿, cleartable() 來清除所有方塊開新遊戲.

    如果 checkRow() 回傳 True, 就代表有人勝利, 這時候我們可以利用 turn 來判別誰贏. 因為玩家畫了圈叉以後會將 turn 變數替換, 因此實際勝利的是上一位玩家, 這地方要注意一下. 如果確定有一方勝利則跳出訊息視窗, 並且詢問玩家是否要再玩一場 (開新遊戲, 如果玩家點 就會清除所有的方塊, 開始新的回合).

    如果 checkRow() 回傳 False, 代表沒有人勝利, 這時候就要呼叫 tableIsFull() 函式確定方塊是否都被填滿. 如果方塊全部都被填滿而又沒有人勝利, 那就是平手 - 跳出訊息視窗並且詢問玩家是否要再開新回合.

    現在我們要來一一完成這些函式, 先從 checkRow() 開始:

    function checkRow()
    {
      
      for(i=0; i<=2; i++)
      {  
        // 列
        if(eval("compare("+"ttt"+i+"0"+","+"ttt"+i+"1"+","+"ttt"+i+"2"+")"))
          return true;
          
      
        // 對角線
        if(eval("compare("+"ttt"+"0"+i+","+"ttt"+"1"+"1"+","+"ttt"+"2"+(2-i)+")"))
          return true;
    
        // 欄
        if(eval("compare("+"ttt"+"0"+i+","+"ttt"+"1"+i+","+"ttt"+"2"+i+")"))
          return true;
      }
      return false;
        
    }
    
    


    checkRow() 函式中有一個 for迴圈, 會從 0 跑到 2.

    這邊我們再假設會有個 compare() 函式, 確認輸入的三個方塊都是同一種符號.

    上面的註解應該不難懂, 第一個 if/else 會確認一列, 第二個 if/else會確認一調對角線, 第三個 if/else 會確認一欄. 這個 for 迴圈會執行三次, 三次跑完就檢查完了三個列, 三個欄 和兩條對角線 (當然中間有重複一點點, 這部份比較沒關係).

    之前沒有介紹過的是 eval() 函式. 他是 Javascript 的內建函式, 作用是將一段字串當作Javascript程式碼執行. 由於這段程式是用迴圈去逐一檢查方塊, 因此需要將迴圈的變數 i 與其他字串重組成類似 compare(ttt01, ttt11, ttt12) (即檢查第一列的三個方塊是否是同樣的符號), 然後再執行.

    checkRow() 函式只要確定有三個相同的符號連成一條線, 就會回傳 True.

    值得注意的一點就是一旦回傳了一個值函式就會結束執行, 就算迴圈沒有跑完也會結束.

    在這裡最好先完成 compare() 函式以免忘記:

    function compare(obj0, obj1, obj2)
    {
      if(obj0.innerText != " " && obj1.innerText != " " && obj2.innerText != " ")
        if(obj0.innerText == obj1.innerText && obj1.innerText == obj2.innerText)
          return true;
    
      return false;  
    }
    


    這個compare函式會接受三個物件 (三個方塊), 然後進行檢查. 如果三個符號都是圈或都是叉, 就會回傳 True, 否則回傳 False.

    接著要寫 tableIsFull() 函式:

    
    function tableIsFull()
    {
      for(i=0; i<=2; i++)
        for(j=0; j<=2; j++)
          if(eval("ttt" + i + j).innerText == " ")
            return false;
      return true;
    }
    
    


    這個函式就比較簡單, 他會跑一個巢狀迴圈 (每一列都會跑三個欄) 來確定每一個方塊都被填滿, 否則回傳 False;

    最後的 clearTable() 函式最簡單了:

    function clearTable()
    {
      for(i=0; i<=2; i++)  
        {
        eval("ttt" + i + "0" + ".innerHTML = ' ';")
        eval("ttt" + i + "1" + ".innerHTML = ' ';")
        eval("ttt" + i + "2" + ".innerHTML = ' ';")
      }
    
    }
    


    只要將每一列的三個欄的數值都改為一個空白字元就可以了.

    這樣就大功告成囉!

    現在我們來組合之前寫的函式:

    
    
    <script language="Javascript">
    <!--
    var turn = 0;
    
    function playRound(objDest)
    {
      if(objDest.innerText == " " && turn == 0)
      {  
        objDest.innerText = "O";
        turn = 1;
      }
      else if(objDest.innerText == " " && turn == 1)
      {
        objDest.innerText = "X";
        turn = 0;
      }
      else
      {
        confirm("方塊已經被佔用了!");
      }
    
      if(checkRow())
      {
        if(turn == 0)
        {
          if(confirm("玩家2贏了!\n\n要再玩一場嗎?"))
            clearTable();
        }
        else if(turn == 1)
        {
          if(confirm("玩家1贏了!\n\n要再玩一場嗎?"))
            clearTable();
        }
      }
      else if(tableIsFull())
      {
        if(confirm("平手\n\n要再玩一場嗎?"))
          clearTable();
      }
    }
    
    
    
    function clearTable()
    {
      for(i=0; i<=2; i++)  
      {
        eval("ttt" + i + "0" + ".innerHTML = '&nbsp;';")
        eval("ttt" + i + "1" + ".innerHTML = '&nbsp;';")
        eval("ttt" + i + "2" + ".innerHTML = '&nbsp;';")
      }
    
    }
    
    
    function checkRow()
    {
    
      for(i=0; i<=2; i++)
      {  
    
        // 列
        if(eval("compare("+"ttt"+i+"0"+","+"ttt"+i+"1"+","+"ttt"+i+"2"+")"))
          return true;
          
      
        // 對角線
        if(eval("compare("+"ttt"+"0"+i+","+"ttt"+"1"+"1"+","+"ttt"+"2"+(2-i)+")"))
          return true;
    
        // 欄
        if(eval("compare("+"ttt"+"0"+i+","+"ttt"+"1"+i+","+"ttt"+"2"+i+")"))
          return true;
    
      }
      return false;
    }
    
    function tableIsFull()
    {
      for(i=0; i<=2; i++)
        for(j=0; j<=2; j++)
          if(eval("ttt" + i + j).innerText == " ")
            return false;
      return true;
    }
    
    function compare(obj0, obj1, obj2)
    {
      if(obj0.innerText != " " && obj1.innerText != " " && obj2.innerText != " ")
        if(obj0.innerText == obj1.innerText && obj1.innerText == obj2.innerText)
          return true;
    
      return false;  
    }
    
    -->
    </script>
    
    <h3>井字遊戲</h3>
    玩家1: O<br>
    玩家2: X<br><br>
    <table border=1 style="border-collapse:collapse;width:150px;height:150px" bordercolor="#000000">
      <tr>
        <td width=50 align="center" id="ttt00" onClick="playRound(this);">&nbsp;</td>
        <td width=50 align="center" id="ttt01" onClick="playRound(this);">&nbsp;</td>
        <td width=50 align="center" id="ttt02" onClick="playRound(this);">&nbsp;</td>
      </tr>
    
      <tr>
        <td width=50 align="center" id="ttt10" onClick="playRound(this);">&nbsp;</td>
        <td width=50 align="center" id="ttt11" onClick="playRound(this);">&nbsp;</td>
        <td width=50 align="center" id="ttt12" onClick="playRound(this);">&nbsp;</td>
      </tr>
    
      <tr>
        <td width=50 align="center" id="ttt20" onClick="playRound(this);">&nbsp;</td>
        <td width=50 align="center" id="ttt21" onClick="playRound(this);">&nbsp;</td>
        <td width=50 align="center" id="ttt22" onClick="playRound(this);">&nbsp;</td>
      </tr>
    
    </table>
    
    


    現在這個井字遊戲除了將圈叉畫在方塊上以外, 還會在玩家每個動作後檢查是否有一方勝利 或是 雙方平手.
    這樣一來功能就完善了.

    這個井字遊戲雖然不難寫, 但是卻可以激發一些基礎的程式邏輯和演算法的思考, 也是一個挺好玩的小程式.

    小弟寫到這邊先停筆了, 希望這篇教學對您有所幫助.

    最後更新日期: 6/3/2004 4:37:30 AM