iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 22
1
Modern Web

關於你關於我關於phaser系列 第 22

Day 22 連線五子棋 ~ 關於實作五子棋規則

  • 分享至 

  • xImage
  •  

我的習慣是(雖然不知道好不好,因爲之前的塔防就是...),會先將遊戲的雛形做在本地端,可以玩後再開始擴增或實作我要的最終版本,所以我們就先來簡單做一個五子棋的遊戲吧

老話,先初始化我們的遊戲,並載入圖片

var config = {
  type: Phaser.AUTO,
  width: 600,
  height: 600,
  backgroundColor: 0x705045,
  scene: {
    preload: preload,
    create: create,
  }
}

var game = new Phaser.Game(config)

function preload() {
  this.load.image('black', 'assets/black.png')
  this.load.image('white', 'assets/white.png')
}

function create(){

}

對我來說五子棋有幾個步驟:

  1. 畫出棋盤
  2. 判斷下的位置
  3. 判斷換誰下
  4. 判斷勝利

畫出棋盤分成兩個部分,看得到的與看不到的,我們都知道五子棋是要下在線的交叉點,但是對程式來說他下的那個是佔一格位置不是四個各佔1/4,所以這邊就分成兩個部分,我們用一個二維陣列裝程式判斷的位置,再來將線畫在格子的中間,來畫出我們的棋盤

var checkerboard = createCheckerboard()

function createCheckerboard() {
  let checkerboard = []
  for (let i = 0; i < 20; i++) {
    checkerboard[i] = new Array()
    for (let j = 0; j < 20; j++) {
      checkerboard[i][j] = 0
    }
  }
  return checkerboard
}

function drawCheckerboard(graphics) {
  graphics.lineStyle(1, 0xffffff, 1)
  for (let i = 0; i < 20; i++) {
    graphics.moveTo(0, 14.5 + i * 30);
    graphics.lineTo(600, 14.5 + i * 30)
  }
  for (let j = 0; j < 20; j++) {
    graphics.moveTo(14.5 + j * 30, 0)
    graphics.lineTo(14.5 + j * 30, 600)
  }
  graphics.strokePath()
}

function create(){
  let path = this.add.path(0, 15)
  let graphics = this.add.graphics()
  drawCheckerboard(graphics)
}

再來判斷下的位置,這邊要提醒,這個看似一個第一象限的 x y 座標圖,但他本質是一個二維陣列,所以在計算格子與之後的計算勝利時,要記得,往上下是修改 第幾列 的值(往上是減往下是加),往左右是修改 第幾行 的值(往左是減往右是加),那我們就來下棋了

這邊下棋的方式與塔防的很類似,先判斷點擊的位置是在二維陣列的那一塊,然後判斷是否有東西,沒有就可以下,並且加在二維陣列裡面與渲染到畫面

var self
function create(){
    self = this
    this.input.on('pointerdown',putChess)
}
function putChess(pointer){
  let i = Math.floor(pointer.y / 30)
  let j = Math.floor(pointer.x / 30)
  if(isEnpty(i,j)){
      checkerboard[i][j] = 'o'
      self.add.image(15 + j * 30, 15 + i * 30, 'black')
  }
}
function isEnpty(i,j){
    return checkerboard[i][j] === 0
}

到這邊就可以在棋盤上一直加黑棋,並且對應的二維陣列會有一個 o 代表黑棋

再來當然是可以白棋黑棋互相下,所以對下棋的地方做一點修改,並有一個狀態表示目前是誰的可以下,並在每次下完後可以換顏色

var status = 'o'
function putChess(pointer){
    if(isEnpty(i,j)){
        if(status === 'o'){
            checkerboard[i][j] = 'o'
            self.add.image(15 + j * 30, 15 + i * 30, 'black')
        }else{
            checkerboard[i][j] = 'x'
            self.add.image(15 + j * 30, 15 + i * 30, 'white')
        }
        status = status === 'o' ? 'x' : 'o'
    }
}

最後就是最麻煩也是最關鍵的地方,如何判斷勝利,大家可以去 google 看看相對於五子棋的其他棋類的勝利判斷方式難易度(以四子棋是最容易的,依序是五子棋、暗棋、跳棋、西洋棋與軍棋差不多、最後是圍棋),這個不止在於規則的定製,這個除了規則之外最重要的還有 AI 製作的難易度,因爲電腦要贏,就是要知道誰什麼時候會贏、要怎麼下防止贏、怎樣下才會贏,而以五子棋爲例,有兩種判斷方式:

  1. 每一次下完就掃描棋盤上的所有位置,並作判斷是否有人贏(圍棋就是這樣做的)
  2. 因爲會贏一定與最後下的那一個點有關係,所以每次判斷以下的那個點的上下、左右、斜角是否有五個或以上的連線

而我是選擇第二種方式,雖然不優雅但用土法鍊鋼的方式也可以判斷勝利,這邊就以一個方向爲舉例:

以左右爲例,當我下了一個棋子,我會以自己下的那個點爲主,因爲是左右,所以是固定列,變動行的加減,而判斷方式爲跑一個迴圈,如果往左(j - x)一個跟自己的顏色一樣,就先加 1 並且繼續往左(j - x),直到遇到不同顏色或是爲空,反之往右則(j + x)一樣,而最後會回傳是否有超過 5 ,因爲五子棋是 5 個或以上就贏了,所以會回傳左右找有沒有找到大於 5 的 true 或 false

//左右
function horizontalWin(i, j) {
  let count = 1
  for (let x = 1; x < 5; x++) {
    if (checkerboard[i][j] === checkerboard[i][j - x]) {
      count += 1
    } else {
      break
    }
  }
  for (let x = 1; x < 5; x++) {
    if (checkerboard[i][j] === checkerboard[i][j + x]) {
      count += 1
    } else {
      break
    }
  }
  return count >= 5
}

當然其他方向的就都一樣的意思啦,但是要注意的地方有一個,小心邊界的大小,這邊遇到的問題會在之後講過程遇到的問題一起討論與解決,但還是跟大家說要注意不要讓第一個數字(行)超出了範圍,所以要先做一個判斷:

for (let y = 1; y < 5; y++) {
    if (i - y < 0) {
      break
    }
    //...
}
//在有可能超過的地方先判斷行是否會超出範圍,不會再做接下來的判斷,會就直接跳出迴圈

如果沒問題就可以自己跟自己下棋啦嗚嗚邊緣人
我的 github 有完整的勝利判斷,但是是在 server 的檔案裡面,原因是什麼明天會跟大家講的


上一篇
Day 21 連線五子棋 ~ 關於socket
下一篇
Day 23 連線五子棋 ~ 關於 server
系列文
關於你關於我關於phaser30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言