作業#40 模仿Google Search小遊戲App-(三)圈圈叉叉

目的:練習製作出圈圈叉叉遊戲

這個是參考Peter的投影片做出來的,歡迎有興趣的人可以點進去文章最後的連結看看,接下來會來按照我跟著做的順序跟大家分享

使用UIBezierPath繪製井字

使用UIViewPropertyAnimator製作動畫

使用UIPanGestureRecognizer移動圈圈叉叉

使用CGRect 的 function intersection(_:)識別圈叉是否與格子重疊

使用.allSatisfy回傳檢查結果

使用CABasicAnimation做出畫線動畫

畫面

井字網格:UIBezierPath繪製

九宮格:UIView

圈圈叉叉:UILabel

資訊欄按鈕:UIButton

井字網格

在這九宮格的背後有一個大的UIView,將這個UIView使用自訂的類別,在裡面覆寫draw,用UIBezierPath畫出井字網格

  1. 先聲明兩個常數,一個是我們九宮格每格的寬度一個是間隔寬,用來計算下筆的點的

2.然後設定下筆的點,是在大的View中的哪個點,可用剛剛聲明的兩個常數找到,特別的是下筆的位置必須在間隔的中間所以需要間隔寬/2

關於UIBezierPath可參考下方連結:

資訊欄

我們會有一個資訊欄,點選下方按鈕資訊欄會跳出,資訊欄內會有收起的按鈕,平常顯示遊戲規則,在有玩家勝利後也會跳出勝利通知,在storyboard製作好這個view後把他移動至上方到螢幕外,然後自訂一個類別給它

重點

  1. 把infoView上的Label拉到自訂的類別中,需要先自己在上面打好@IBOutlet再拉線到元件上
  2. UIViewPropertyAnimator是一種動畫製作的方式,可以對view的變動做動畫,詳細下方會有連結可以點進去看看
  3. 這邊所做出的function,都可以在controller中用連接View的IBOutlet做使用

關於UIViewPropertyAnimator連結:

輪到圈還是叉,進行動畫

輪到圈還是叉的時候,可動的那方會旋轉半圈並且不透明

  1. 傳入一個型別為UILabel的參數,輪到哪方的時候,就會把圈或叉的Label傳進去
  2. 使用UIViewPropertyAnimator,在裡面執行讓Label整個旋轉半圈,並且alpha值變1,我們一開始預設兩個Label的isUserInteractionEnabled都是false,alpha值都只有0.5會呈現半透明
  3. 然後加入addCompletion,動畫完成後執行什麼,打開isUserInteractionEnabled,然後加入手勢Pan Gesture,然後把Label放置整個view到最前面
  4. 最後要執行動畫.startAnimation()

移動圈叉Label

移動Label且如果沒有移動到網格中會自動回到原位

  1. 得到目前移動的Label
  2. 判斷目前使否有在移動中,如果正在移動中就用CGAffineTransform改變目前Label的位子,可以從Action的sender也就是手勢本身的translation得到目前移動的距離
  3. 如果停止滑動了,則開始判斷目前是否滑動到九宮格內,如果沒有就讓Label回到原位,如果有的話就執行讓Label放進格子內
  4. 放進格子前要先判斷是要放進哪個格子,先聲明一個判別目前滑動的區域佔哪個格子較多的變數,一個optional的UIView存儲要放進去的格子
  5. 然後用迴圈先用intersection得到目前Label跟第一個格子重疊的區域,然後算出重疊的面積,如果重疊的面積大於目前最大重疊面積的格子,就把最大重疊面積改為新的這個,再將要放入的格子改為這個,以此類推檢查完九個格子,看目前重疊部分最多的是哪格,就放入哪個格子中

將Label放進格子

這裡會傳入兩個參數,哪個Lable要放進哪個格子

  1. 先聲明一個變數存儲原始點
  2. 接著用UIViewPropertyAnimator執行動畫,這裡用runningPropertyAnimator,是希望在創建動畫後立即執行
  3. 在動畫中執行,用.identity讓Label完全不改變留在原地,接著原始點存進Label的中心點,然後Label的中心點變成格子的中心點,就留在格子內並對齊中間
  4. 動畫結束後輪到另一個玩家

玩家交換

接在移動玩Label進格子後,要輪到另一個玩家,傳入兩個參數一個是原本的Label一個是原始點

  1. 先做出一個回傳新Label的function,製作一個跟原本Label一模一樣的Label
  2. 在輪到另個玩家的function中,首先用建立新Label的function回傳一個新的Label,然後Label的中心點等於剛剛存好的原Label的中心點讓他固定在原位,然後加入到view上
  3. 接著判斷這個原本的Label是圈還是叉,如果是圈圈就把新的Label給他,並且輪到叉叉

判定輸贏

建立一個專門判定勝負的Model,當我們需要判定時就從這個類別中取用方法判讀

  1. 先列舉我們有的兩種東西,圈或叉
  2. 然後建立一個陣列有九個能裝圈或叉,一開始先nil因為一開始不會以東西,是後面慢慢加進來的,要加上lazy才能許我們晚點把東西加入
  3. 然後再建立一個陣列裝進八個會連線成功的可能

winner

  1. 然後用{get}建立winner屬性,只要讀取就可以得到誰是贏家
  2. winner產生的方法,用迴圈把這八種連線成功的第一格所顯示的ox拿出來跟接下來的第二第三點做比對,用.allSatisfy可以得到回傳的Bool,如果都一樣就會回傳true
  3. 如果是true,winner就會是剛取得的第一格所顯示的ox

isFull

判定是否目前全滿格的狀態,一樣用.allSatisfy把每格是否是空的檢查一遍

isTie

是否平手,如果全滿又沒有人獲勝的時候就是平手

isSquareEmpty

檢查某格是否是空的

occupy

在squares的指定位子放進ox

clear

把紀錄ox位子的squares清空

判定輸贏加入到Controller中

  1. 先在移動放置Label的function中加入targetIndex,獲得格子的序號
  2. 然後在下面判定如果這格是空的再加入Label

在放進格子的function中加入一個參數index,是為了要放進completion裡的finishCurrentTurn這個function內

  1. 先在controller加入一個能夠裝UILabel的空陣列,為了要紀錄使用過的Label
  2. 然後聲明一個常數紀錄下一個輪到哪個Label
  3. 最後先判定輸贏再決定有沒有輪到下一個

重新開始

  1. 使用grid.clear將紀錄格子位置的ox清空
  2. 把格子內的Label移除
  3. 把紀錄Label的Array也清空
  4. 然後再從o開始新遊戲

加入勝利線

  1. 繪製勝利線的function傳入線的起點以及終點
  2. 第一部分是畫線,第二部分是設定動畫,第三部分是動畫結束後做什麼

在輪到下一個玩家的function中把原本的判讀方式拿掉改成如果winninerLines裡面不是空的就將線的連法的起點跟終點帶入,因為如果有勝利者才會不是空的,所以也可拿來判別是否有勝利者

練習心得

原本以為可以把圈圈叉叉跟輪盤遊戲放一起,結果光要看懂圈圈叉叉的範例就快一天了,內容也是很長所以把輪盤遊戲放到第四篇去,覺得最難的還是如何判定是否勝利的部分,一開始看code真的讀很久才懂意思,我以為最難做的是轉輪盤的遊戲,想不到大魔王竟然是圈圈叉叉這個從小就會常玩又很簡單的遊戲,我把對Peter投影片的理解都寫在這裡面,如果有任何誤解的地方歡迎指正,非常感謝😊

Peter投影片連結:

參考連結:

第一篇 畫面、collectionView

第二篇 銅板遊戲、骰子遊戲

第四篇 輪盤遊戲+GitHub連結

--

--