iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 9
0

勝負判斷分析

昨天完成了 row 方向和 column 方向的勝負判斷,今天我們要來完成右斜和左斜方向的勝負判斷,我這邊先把這兩個方向命名為 forwardSlash( / ), backSlash( \ )。

左斜勝負判斷分析

首先我們要來分析一下左斜的勝負判斷該怎麼做,下面我畫了輔助線來幫助我們看圖。
forward-slash

參考 Day08 的做法,如果我們也可以產生出一個結構為 arrs 的 array of array,我們就可以用同樣的函數來順利的完成勝負判斷。

再次說明一下,這個函數有三個輸入

  1. blocks
    a. 首先我們需要知道每一個格子的資訊,blocks 裡面包含每個格子的 id 以及他的狀態,就是他上面是圈圈,還是叉叉,還是都沒下。
  2. arrs
    a. 這是我們今天要找到的目標,這個 array of array 幫助我們一一的來檢查 blocks 裡面的資料是不是已經產生贏家。
  3. winnerCondition
    a. 由於我們是伸縮自如的圈圈叉叉,獲勝條件自己控制,所以這個函數也需要知道目前的獲勝條件是需要幾顆棋子連成一條線。
const result = getWinCase(blocks, arrs, winnerCondition);

透過這個函數,我們希望能夠得到這樣的結構的物件。這個結構的物件可以幫助我們知道目前是否已經產生贏家?若有,誰是贏家?以及這個贏家他連成幾條線?這條線是哪幾個格子連成線的?

// 這是圈圈贏,連成一條線的範例結果
const result = {
    1: [ [0, 1, 2] ],
    -1: []
};

// 這是圈圈贏,連成兩條線的範例結果
const result = {
    1: [ [0, 1, 2], [2, 4, 6] ],
    -1: []
};

所以今天的重點會放在如何找出相對應方向的 arrs。
我們先來看一下左斜(forwardSlash)是不是有一些等差等比,或其他的規則在裡面,所以先把狀況都列出來以便觀察。

考慮 3x3 的情況,下面是我們所期待得到的 arrs

arrs = [
	[0],
	[1, 3],
	[2, 4, 6],
	[5, 7],
	[8],
]

考慮 4x4 的情況,下面是我們所期待得到的 arrs

arrs = [
	[0],
	[1, 4],
	[2, 5, 8],
	[3, 6, 9, 12],
	[7, 10, 13],
	[11, 14],
	[15],
]

如果找不到規律,可以多列幾個出來觀察。
下面畫了輔助線的圖片是我找到的線索
circle-analysis

triangle-analysis

多列幾個之後,我跟大家分享我觀察到的現象,以及我接下來的做法。
不管是幾乘幾的棋盤,我覺得 arrs 都可以拆開來看成兩個部分,拆解的部分,可以依據我上面的圖來劃分。
以 3x3 棋盤為例,我覺得 arrs 可以拆成下面這兩部分:

// arrs-1
[
	[0],
	[1, 3],
	[2, 4, 6],
]

// arrs-2
[
	[5, 7],
	[8],
]

以 4x4 的棋盤為例,也可以依此類推:

// arrs-1
[
	[0],
	[1, 4],
	[2, 5, 8],
	[3, 6, 9, 12],
]

// arrs-2
[
	[7, 10, 13],
	[11, 14],
	[15],
]

如果這樣拆開來看的話,我們可以發現 arrs 中每一個小 ar r的第一個數字,彼此是等差關係,而且單看一個 arr 的話,數字之間也是等差關係

所以根據這樣的關係,我可以分別計算出 arr-1arr-2 後,做合併,就可以得到我們的目標 arrs 了。

所以 forwardSlash 的 arrs ,我的計算方法如下:

// ex: 得到 [0, 1, 2]
const firstHeaders = _.range(0, gameScale);

// ex: 得到 [5, 8]
const secondHeaders = _.range(gameScale * 2 - 1, gameScale * gameScale, gameScale);

// 得到 arrs-1
const firstArr = firstHeaders.map((header, index) => (
    _.range(header, index * gameScale + 1, gameScale - 1)
));

// 得到 arrs-2
const secondArr = secondHeaders.map((header, index) => (
    _.range(header, header + (secondHeaders.length - index) * (gameScale - 1), gameScale - 1)
));

// 合併 arrs-1 及 arrs-2
const arrs = [...firstArr, ...secondArr];

右斜勝負判斷分析

根據同樣的方法, backSlash 的 arrs 也可以迎刃而解
back-slash

同樣的,考慮 3x3 的情況,下面是我們所期待得到的 arrs

arrs = [
	[2],
	[1, 5],
	[0, 4, 8],
	[3, 7],
	[6],
]

考慮 4x4 的情況,下面是我們所期待得到的 arrs

arrs = [
	[3],
	[2, 7],
	[1, 6, 11],
	[0, 5, 10, 15],
	[4, 9, 14],
	[8, 13],
	[12],
]

我們一樣把 3x3 的 arrs 拆成兩部份

// arr-1
[
	[2],
	[1, 5],
	[0, 4, 8],
]

// 以及 arr-2
[
	[3, 7],
	[6],
]

拆開之後,也能夠發現 arrs 中每一個小 arr 的第一個數字,彼此是等差關係,而且單看一個 arr 的話,數字之間也是等差關係

同樣的伎倆,我們又破解了 backSlash 的 arrs 了!

合併結果判斷勝負

勝負判斷方法的最後,我們要把所有方向的 arrs 合併
getWinner
tic-tac-toe/src/containers/TicTacToe/utils.js

這邊我的勝負判斷方法的函數名稱是 getWinner(),如下:

const isWin = getWinner(blocks, gameScale, winnerCondition);

我希望這個函數有有三個輸入和一個輸出,三個輸入是我們熟悉的參數,是為了計算出各個方向(ex: row, column, forwardSlash, backSlash)的arrs所需要的,輸出是一個命名為 isWin 的物件,這邊來說明一下這個物件。

isWin:

  1. 物件的內容如下:
isWin = { winner: ‘’, winCaseArr: [], isGameFinished: false }
  1. 每下完一顆棋,我們就會來判斷一下獲勝者是否誕生,在 isWin 這個物件裡面,我儲存三個參數,如果獲勝者已經產生了,我們會把它存在 winner 裡,1代表圈圈,-1代表叉叉。
  2. 再來因為我們需要標示獲勝者是哪幾個格子連成一直線,把這些 id 記錄下來,同一條線的放進一個陣列,這一條線我稱為 winCase 。再把這個陣列放進 winCaseArr 。因為獲勝者誕生的時候,可能他不只連成一條線,可能兩條線,三條線,所以他是一個 array of array ,也就是 winCaseArr 裡面有很多 winCase 。
  3. 最後我們用 isGameFinished 來標示遊戲是否結束,結束的狀況有三種,一個是圈圈贏,一個是叉叉贏,一個是平手,這三種狀況其中一個發生的時候, isGameFinished 會標示成 true。

透過 isWin 這個物件,我們可以來判斷勝負

  1. 輸贏還沒產生: winner 是空的,且 isGameFinished 是 false 。
  2. 圈圈贏: winner 是 1 ,透過 winCaseArr 可以知道他連成哪一條線。
  3. 叉叉贏: winner 是 -1 ,透過 winCaseArr 可以知道他連成哪一條線。
  4. 平手: winner 是空的,且 isGameFinished 是 true 。

info-board
tic-tac-toe/src/containers/TicTacToe/components/InfoBoard/index.js

有了 isWin 物件之後,我們可以在 <InfoBoard /> 裡面顯示出勝負的狀態,也可以標示連成一條線的棋子,以下是加入 isWin 參數之後的成果展示:
winnerDemo

Tic Tac Toe - Github


上一篇
Day08 - Tic Tac Toe篇:勝負判斷方法(1/2)
下一篇
Day10 - Tic Tac Toe篇:Single Play機制
系列文
以經典小遊戲為主題之ReactJS應用練習30

尚未有邦友留言

立即登入留言