#19 骰子轉轉轉 & 紅黑單雙

目的:利用亂數、for-in迴圈、if-else判斷來做出一個完整的擲骰子APP

說到骰子遊戲,在酒局裡常玩的除了吹牛跟比大小,另外想到的就是我這次做的主題『紅黑單雙』,這也算是經典中的經典,當初因為這些遊戲不知喝了多少又吐了多少~🤢🤢🤢

遊戲規則:

基本規則: 
紅色=點數1、4
黑色=點數2、3、5、6
單數=點數1、3、5
雙數=點數2、4、6
大=點數4、5、6
小=點數1、2、3
遊戲規則:
舉例兩個玩家開始遊戲,先決定誰先開始喊牌
搖骰後喊牌,喊牌者需把骰盅打開喊
其他玩家等待喊牌者喊完才開盅。
所有玩家完成拿掉骰子的動作後
所有玩家再把剩餘的骰子,放回骰盅重骰
然後換下個玩家喊牌。
當骰盅只剩1顆骰子,喊牌者需盲喊,不得看牌喊。
盲喊若喊到自己盅內的點數,判定自爆為輸家!
最先被清掉全部骰子為輸家!

APP功能介紹:

  • 按下搖骰Button,變更骰子點數
  • 紅黑單雙Button可以將骰子拿掉
  • 按下再來一局Button,將骰子重新加回來
  • 瞇牌開關可看到骰盅裡的骰子點數

重點技術:

  • 利用if-else判斷要拿掉的骰子點數
  • 利用CABasicAnimation動畫效果,讓畫面更豐富
  • 利用for-in迴圈讓程式更乾淨

搖骰Button

建立一個儲存範圍1~6的亂數的常數,為了要讓這個常數跟圖片相互呼應,圖片名稱裡也要有1~6的數字,這樣在代入時image才會跟著亂數改變,利用for-in迴圈讓每個imageView都跑過一次

@IBAction func play(_ sender: Any) {
for index in 0...5 {
let dicePoint = Int.random(in: 1...6)
diceImageViews[index].image = UIImage(named: "dice\(dicePoint)")
rotateDice(diceImage: diceImageViews[index])
}
diceCupImageView.shake()
shakeSound()
}

再來一局Button

將畫面還原,把骰子加回到畫面上,insertSubview 可以指定將UIView插入哪一層,如果用addSubview 會擋到其他圖片

@IBAction func replay(_ sender: Any) {
//將骰子重新加回到畫面上
var dicePoint = Int.random(in: 1...6)
diceImageViews[0].frame = CGRect(x: 131, y: 333, width: 50, height: 50)
diceImageViews[0].image = UIImage(named: "dice\(dicePoint)")
view.insertSubview(diceImageViews[0], at: 1)
dicePoint = Int.random(in: 1...6)
diceImageViews[1].frame = CGRect(x: 189, y: 438, width: 50, height: 50)
diceImageViews[1].image = UIImage(named: "dice\(dicePoint)")
view.insertSubview(diceImageViews[1], at: 1)
dicePoint = Int.random(in: 1...6)
diceImageViews[2].frame = CGRect(x: 131, y: 418, width: 50, height: 50)
diceImageViews[2].image = UIImage(named: "dice\(dicePoint)")
view.insertSubview(diceImageViews[2], at: 1)
dicePoint = Int.random(in: 1...6)
diceImageViews[3].frame = CGRect(x: 247, y: 333, width: 50, height: 50)
diceImageViews[3].image = UIImage(named: "dice\(dicePoint)")
view.insertSubview(diceImageViews[3], at: 1)
dicePoint = Int.random(in: 1...6)
diceImageViews[4].frame = CGRect(x: 189, y: 361, width: 50, height: 50)
diceImageViews[4].image = UIImage(named: "dice\(dicePoint)")
view.insertSubview(diceImageViews[4], at: 1)
dicePoint = Int.random(in: 1...6)
diceImageViews[5].frame = CGRect(x: 247, y: 418, width: 50, height: 50)
diceImageViews[5].image = UIImage(named: "dice\(dicePoint)")
view.insertSubview(diceImageViews[5], at: 1)
loseLabel.alpha = 0
beerImageView.alpha = 0
diceCupImageView.alpha = 1
peepSwitch.isOn = false
}

紅黑單雙Button

利用imageView的圖片來判斷想要移除的點數,removeFromSuperview() 可以將imageView從畫面中移除,一樣利用for-in迴圈讓每個骰子都可以被判斷到

@IBAction func redPoint(_ sender: Any) {
//紅色骰子拿掉
for index in 0...5 {
if diceImageViews[index].image == UIImage(named: "dice1") {
//diceImageViews[index].isHidden = true
diceImageViews[index].removeFromSuperview()
} else if diceImageViews[index].image == UIImage(named: "dice4") {
//diceImageViews[index].isHidden = true
diceImageViews[index].removeFromSuperview()
}
}
judgedToLose()
}

Switch開關

@IBAction func peep(_ sender: UISwitch) {
if sender.isOn == true {
diceCupImageView.alpha = 0.8
} else {
diceCupImageView.alpha = 1
}
}

旋轉骰子

CABasicAnimation 可以讓物件實現動畫,(keyPath: “transform.rotation”) 可以讓物件有旋轉的動畫效果

func rotateDice(diceImage: UIImageView) {
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotateAnimation.fromValue = 0
rotateAnimation.toValue = Float.pi / 180 * 1080
rotateAnimation.duration = 1
rotateAnimation.speed = 1
diceImage.layer.add(rotateAnimation, forKey: nil)
}

搖骰聲

要呼叫AVPlayer必須先import AVFoundation

func shakeSound() {
let player: AVPlayer?
if let url = Bundle.main.url(forResource: "diceShake", withExtension: "mp3") {
player = AVPlayer(url: url)
player?.play()
}
}

判斷畫面上是否有骰子

原本是想用image等於nil去做判斷,結果不行,後來問Peter才知道image不可能為nil,因為前面移除imageView的方法是用removeFromSuperview() ,必須用superView去做判斷,接著利用CABasicAnimation 做出輸掉遊戲的動畫,(keyPath: “position”) 可以讓物件移動,moveAnimation.fillMode = .forwardsmoveAnimation.isRemovedOnCompletion = false 可以讓物件停留在動畫結束的地方

func judgedToLose() {
if diceImageViews[0].superview == nil, diceImageViews[1].superview == nil, diceImageViews[2].superview == nil, diceImageViews[3].superview == nil, diceImageViews[4].superview == nil, diceImageViews[5].superview == nil {
print("you lose")
loseLabel.alpha = 1
beerImageView.alpha = 1
let moveAnimation = CABasicAnimation(keyPath: "position")
moveAnimation.fromValue = [220, -200]
moveAnimation.toValue = [220, 350]
moveAnimation.fillMode = .forwards
moveAnimation.isRemovedOnCompletion = false
moveAnimation.duration = 2
beerImageView.layer.add(moveAnimation, forKey: nil)
moveAnimation.fromValue = [220, -200]
moveAnimation.toValue = [220, 200]
loseLabel.layer.add(moveAnimation, forKey: nil)
diceCupImageView.alpha = 0
}
}

晃動手機,進行搖骰動作

motionBegan為系統偵測到手機晃動時觸發的方法

override func motionBegan(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {
if motion == .motionShake {
for index in 0...5 {
let dicePoint = Int.random(in: 1...6)
diceImageViews[index].image = UIImage(named: "dice\(dicePoint)")
rotateDice(diceImage: diceImageViews[index])
}
diceCupImageView.shake()
shakeSound()
}
}

extension擴充

這裡我是參考一位大大寫的文章,extension在UIView的話,大部分的UI元件都可以用這個方法

extension UIView {
func shake(times: Int = 7,deltaX : CGFloat = 15) {
let shakeAnimator = UIViewPropertyAnimator.runningPropertyAnimator(withDuration: 0.1, delay: 0, options: [.curveEaseIn], animations: {
self.layer.setAffineTransform(CGAffineTransform(translationX: deltaX, y: 0))
})
{ (_) in
if times != 0 {
self.shake(times: times - 1, deltaX: -deltaX)
} else {
self.layer.setAffineTransform(CGAffineTransform.identity)
}
}
shakeAnimator.startAnimation()
}
}

Demo

GitHub

--

--