[Day26] swift & kotlin 游戏篇!(8) 小鸡BB-游戏制作-历史纪录

Swift 游戏示意

游戏 小鸡BB

Swift 游戏纪录

最後一个功能是游戏纪录
修改一下Player.swift


import UIKit

class Player: NSObject {
    var point: Int = 1000
    var history: Array<OrderHistory> = []
    
    func addHistory(_  choose: String, _ is_win: Bool, _ newPoint: Int, _ result: ResultResponse){
        self.history.append(OrderHistory(choose, is_win, point, result, newPoint - point))
    }
}

class OrderHistory: NSObject  {
    let choose: String
    let result: ResultResponse
    let is_win: Bool
    let point: Int
    let winPoint: Int
    
    init(_  choose: String, _ is_win: Bool, _ point: Int, _ result: ResultResponse, _ winPoint: Int) {
        self.choose = choose
        self.is_win = is_win
        self.point = point
        self.result = result
        self.winPoint = winPoint
    }
}

然後回到 ViewController.swift
在播放动画时呼叫 player.addHistory
示意图中有提到, 游戏纪录要写在第二个分页
因此我们来新增游戏纪录页面的类别
首先来到这个画面
游戏 小鸡BB
请分别产生这两个档案
游戏 小鸡BB
游戏 小鸡BB
然後设定到UI的Class中
游戏 小鸡BB
游戏 小鸡BB
到 纪录的Cell上 设定Id 成 "Cell"
游戏 小鸡BB
程序码分别如这样

import UIKit

class HistoryViewControllerTableViewController: UITableViewController {
    var history: Array<OrderHistory> = []
    override func viewDidLoad() {
        super.viewDidLoad()
        // 绑定Cell
        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    }

    // 回传要几个table区域
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    //  总共有几个项目
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return  self.history.count
    }

    // 设定每一条table的显示内容  
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 刚刚在 设定的ID "Cell"
        let cellIdentifier = "Cell"
        let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)

        // 设定显示结果
        var newText = "\(indexPath.row+1).  结果:"

        let currentHistory = self.history[indexPath.row]
        
        if currentHistory.is_win {
            newText += "赢 +\(currentHistory.winPoint)"
        } else {
            newText += "输 \(currentHistory.winPoint)"
        }

        cell.textLabel?.text = newText
        return cell
    }
    
    // 回传 table 的 Title
    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return "游戏纪录"
    }

    // 当cell变点击时, 要做的事情
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // 利用Segue开画面 showDetal 前面设定的跳页id
        performSegue(withIdentifier: "showDetail", sender: nil)
    }

    // 跳页前可以做一些事情
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // 判断如果是去 HistoryDetailViewController
        // 就把资料带过去
        
        if segue.destination is HistoryDetailViewController {
            let vc = segue.destination as? HistoryDetailViewController
            let selectedRowPath = self.tableView.indexPathForSelectedRow
            if let selectedRow = selectedRowPath?.row {
                let currentHistory = history[selectedRow]
                vc?.history = currentHistory
            }
        }
        
        
    }
    

}
import UIKit

class HistoryDetailViewController: UIViewController {
    var history: OrderHistory?
    
    @IBOutlet weak var winPointView: UILabel!
    @IBOutlet weak var resultView: UILabel!
    @IBOutlet weak var chooseView: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // 设定画面显示
        if let historyData = self.history {
            print(historyData.choose)
            chooseView.text?.append(" \(self.parseChoose(historyData.choose))")
            resultView.text?.append(" \(historyData.is_win ? "赢" : "输")")
            winPointView.text?.append(" \(historyData.winPoint)")
            if !historyData.is_win {
                view.backgroundColor = UIColor.gray
            }
        }
    }
    
    func parseChoose(_ chooseKey: String) -> String {
        var choose = ""
        
        switch chooseKey {
            case "left_odd":
                choose = "左蓝"
            case "left_even":
                choose = "左红"
            case "right_odd":
                choose = "左蓝"
            case "right_even":
                choose = "左蓝"
            default:
                choose = ""
        }
        
        return choose
    }

}

最後在ViewController.swift 动画播放完後
执行更新纪录的方法
完成~

func updateHistoryPage() -> Void {
    if let tableView = self.tabBarController?.viewControllers?[1] as? HistoryViewControllerTableViewController {
        tableView.history = self.player.history
        tableView.tableView.reloadData()
    }
}

经过了那麽多天的努力
最後我们来看看ViewController.swift 完整的样子

import UIKit

enum EggWapperDirection {
    case Left
    case Right
}
enum HatColor {
    case Red
    case Blue
}


struct KeyFrameOptionItem {
    let startTime: Double // 动画开始时间
    let translationX: CGFloat // 左右位移
    let translationY: CGFloat // 上下跳动
    let rotated: CGFloat // 上下角度
    let scaledX: CGFloat // 水平翻转
    
    init(startTime: Double, translationX: CGFloat, translationY: CGFloat, rotated: CGFloat, scaledX: CGFloat)  {
        let oneDegree = CGFloat.pi / 180 // 透过pi转换rotated角度
        self.startTime = startTime
        self.translationX = translationX
        self.rotated = rotated * oneDegree
        self.translationY = translationY
        self.scaledX = scaledX
    }
}



class ViewController: UIViewController {
    // 按钮
    @IBOutlet weak var left_blue: UIButton!
    @IBOutlet weak var right_blue: UIButton!
    @IBOutlet weak var left_red: UIButton!
    @IBOutlet weak var right_red: UIButton!
    @IBOutlet weak var pointLabel: UILabel!
    @IBOutlet weak var winIcon: UIImageView!
    
    //小鸡 图片 参照
    @IBOutlet weak var ggImg: UIImageView!
    @IBOutlet weak var eggshell_right: UIImageView!
    @IBOutlet weak var eggshell_left: UIImageView!
    @IBOutlet weak var Cloud: UIImageView!
    
    // 游戏画面
    @IBOutlet weak var gameWapper: UIView!
    @IBOutlet weak var lineWapper: UIView!
    @IBOutlet weak var eggWapperRight: UIView!
    @IBOutlet weak var eggWapperLeft: UIView!
    @IBOutlet weak var hat_blue: UIImageView!
    @IBOutlet weak var hat_red: UIImageView!
    
    var lastLineLayer: CAShapeLayer? = nil
    var lastLineInLineLayer: CAShapeLayer? = nil
    
    var lineWapperHeight: CGFloat  = 0
    var lineWapperWidth: CGFloat  = 0
    //小鸡 动画参数
    var chickKeyFrameOptions: Array<KeyFrameOptionItem> = []
    var chooseList: Set<UIButton> = Set<UIButton>()
    
    var player = Player()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.setChickKeyFrameOption()
        self.drawGameLine()
       
    }
    
    override func viewDidAppear(_ animated: Bool) {
        print("viewDidDisappear")
        self.reSetAni()
        
    }
    
    @IBAction func choose(_ sender: UIButton) {
        self.enableAllButton(false)
        
        let session = URLSession(configuration: .default)
        var request = URLRequest(url: URL(string: "http://pinyi.ami-shake.com/gg_order.php")!)
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpMethod = "POST"
        let data = ["choose": self.getChoose(sender), "balance": String(self.player.point)]
      
        do{
            request.httpBody = try JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions())
        }catch let error{
            print("passer data error")
            print(error)
        }
        
        session.dataTask(with: request) { data, response, error in
            if let data = data {
                do {
                   let res = try JSONDecoder().decode(OrderResponse.self, from: data)
                    DispatchQueue.main.async {
                        self.player.addHistory(self.getChoose(sender), res.info.is_win, Int(res.info.balance)  ?? 0, res.info.result)
                        self.playResultFromResponse(res)
                        self.updateHistoryPage()
                    }
                } catch let error {
                   print("error")
                   print(error)
                }
                
            }
        }.resume()
    }
    
    func updateHistoryPage() -> Void {
        if let tableView = self.tabBarController?.viewControllers?[1] as? HistoryViewControllerTableViewController {
            tableView.history = self.player.history
            tableView.tableView.reloadData()
        }
    }
    
    
    func playResultFromResponse(_ res: OrderResponse) -> Void {
        if(res.info.result.start == "left" && res.info.result.end == "odd" ){
            self.playResult(EggWapperDirection.Left, HatColor.Blue, res.info.is_win, res.info.balance)
        }
        if(res.info.result.start == "left" && res.info.result.end == "even" ){
            self.playResult(EggWapperDirection.Left, HatColor.Red, res.info.is_win, res.info.balance)
        }
        if(res.info.result.start == "right"  && res.info.result.end == "odd" ){
            self.playResult(EggWapperDirection.Right, HatColor.Blue, res.info.is_win, res.info.balance)
        }
        if(res.info.result.start == "right"  && res.info.result.end == "even" ){
            self.playResult(EggWapperDirection.Right, HatColor.Red, res.info.is_win, res.info.balance)
        }
    }
    
    func enableAllButton(_ isEnable: Bool) -> Void {
        let buttonList = [self.left_red, self.left_blue , self.right_red, self.right_blue]
        let disableAlpha: CGFloat = 0.5
        
        for button in buttonList {
            button?.isEnabled = isEnable
            button?.alpha = isEnable ? 1 : disableAlpha
        }
    }
    
    func reSetAni() -> Void {
        self.eggshell_left.transform = .identity
        self.eggshell_left.alpha = 1
        self.eggshell_right.transform = .identity
        self.eggshell_right.alpha = 1
        self.eggWapperLeft.transform = .identity
        self.eggWapperRight.transform = .identity
       
        self.displayCloud(true).startAnimation()
        self.displayLastLine(true)
        // 重新设定初始动画
        self.setChickAnimation()
        self.enableAllButton(true)
    }
    
    func playResult(_ eggWapperDirection: EggWapperDirection, _ hatColor: HatColor, _ isWin: Bool, _ newPoint: String)-> Void {
        var hasLastLine = true
        
        if(
            (eggWapperDirection == EggWapperDirection.Right && hatColor == HatColor.Blue) ||
            (eggWapperDirection == EggWapperDirection.Left && hatColor == HatColor.Red)
        ){
            hasLastLine = false
        }
        
        let eggshellAni =  self.openEggAni(eggWapperDirection)
        let cloudAni = self.displayCloud(false)
        let playEggAni = self.playEggAniOnLine(eggWapperDirection, hasLastLine)
        
        playEggAni.addCompletion({ _ in
            self.updatePoint(isWin, newPoint)
            self.reSetAni()
        })
        
        cloudAni.addCompletion({ _ in
            playEggAni.startAnimation()
        })
        
        displayLastLine(hasLastLine)
        
        eggshellAni.startAnimation()
        cloudAni.startAnimation()
        
    }
    
    func updatePoint(_ isWin: Bool, _ newPoint: String)-> Void {
        
        self.winIcon.isHidden = false
        if isWin {
            // AudioServicesPlaySystemSound(1325)
            self.winIcon.image = UIImage(systemName: "hands.sparkles.fill")
        } else {
            // ㄆAudioServicesPlaySystemSound(1324)
            self.winIcon.image = UIImage(systemName: "hand.thumbsdown")
        }
        
        self.updatePointAndDisplayInUI(Int(newPoint) ?? 0)
        
        self.checkIsGameOver()
    }
    
    func checkIsGameOver() -> Void {
        if self.player.point > 0 {
            return
        }
        
        self.alertMessage("游戏结束!", "输了! 游戏即将重启")
        self.updatePointAndDisplayInUI(1000)
    }
    
    func updatePointAndDisplayInUI(_ newPoint: Int) {
        self.player.point = newPoint
        self.pointLabel.text = "Point: \(self.player.point)"
    }
    
    func alertMessage(_ title: String,_ msg: String) -> Void {
        let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
        let okBtn = UIAlertAction(title: "OK", style: .default, handler: nil)
        alert.addAction(okBtn)
        self.present(alert, animated: true, completion: nil)
        
    }
    
    func openEggAni(_ eggWapperDirection: EggWapperDirection) -> UIViewPropertyAnimator {
        return UIViewPropertyAnimator(duration: 0.5, curve: .linear, animations: {
            let egg: UIView! = eggWapperDirection == EggWapperDirection.Right ? self.eggshell_right : self.eggshell_left
            egg.transform = CGAffineTransform(translationX: 30, y: -30).rotated(by: 30 *  CGFloat.pi / 180 )
            egg.alpha = 0
        })
    }
    
    func playEggAniOnLine(_ eggWapperDirection: EggWapperDirection, _ hasLastLine: Bool) -> UIViewPropertyAnimator {
      
        let eggWapperAni = UIViewPropertyAnimator(duration: 3, curve: .linear)
        eggWapperAni.addAnimations {
            UIView.animateKeyframes(withDuration: 0, delay: 0, animations: {
                let eggRunLineKeyFrameOptions = self.getEggRunLineKeyFrameOptions(hasLastLine);
                for option in eggRunLineKeyFrameOptions {
                    UIView.addKeyframe(
                        withRelativeStartTime: option.startTime,
                        relativeDuration: 0.1,
                        animations: {
                            if(eggWapperDirection == EggWapperDirection.Left){
                                self.eggWapperLeft.transform = CGAffineTransform(translationX: option.translationX, y: option.translationY)
                            } else {
                                self.eggWapperRight.transform = CGAffineTransform(translationX: -option.translationX, y: option.translationY)
                            }
                            
                        })
                }
            })
        }
        
        return eggWapperAni
    }
    
    func setChickAnimation() {
        print("setChickAnimation")
        UIView.animateKeyframes(
            withDuration: 4,
            delay: 0.0,
            options: [.repeat, .calculationModeLinear],
            animations: {
                for option in self.chickKeyFrameOptions {
                    UIView.addKeyframe(
                        withRelativeStartTime: option.startTime,
                        relativeDuration: 0.1,
                        animations: {
                            self.ggImg.transform = CGAffineTransform(translationX: option.translationX, y: option.translationY)
                                .rotated(by: option.rotated)
                                .scaledBy(x: option.scaledX, y: 1.0)
                            self.eggshell_right.transform = CGAffineTransform(translationX: 0, y: option.translationY < 0 ? -10 : 0)
                            self.eggshell_left.transform = CGAffineTransform(translationX: 0, y: option.translationY == 0 ? -10 : 0)
                        }
                    )
                }
            },
            completion: nil
        )
    }
    
    func drawGameLine() {
        // 如果已经初始化过 就不再重绘
        if(self.lineWapperHeight > 0){
            return
        }
        // 高度用帽子最小的Y - 鸡蛋最大的Y
        self.lineWapperHeight = hat_blue.frame.minY - eggWapperLeft.frame.maxY
        // 宽度用两个鸡蛋中间的X计算
        self.lineWapperWidth = eggWapperRight.frame.midX - eggWapperLeft.frame.midX
        
        // 左边柱子
        drawLineWithBoderInLineWapper(
            startPoint: CGPoint(x: eggWapperLeft.frame.midX, y:  (eggWapperLeft.frame.maxY - 20)),
            endPoint: CGPoint(x: hat_blue.frame.midX, y:  (hat_blue.frame.minY - 10)),
            lineWidth: 20,
            rectColor: UIColor.init(red: 133/255, green: 240/255, blue: 240/255, alpha: 1),
            boderWidth: 8,
            boderColor: UIColor.init(red: 17/255, green: 152/255, blue: 148/255, alpha: 1)
        )
        // 右边柱子
        drawLineWithBoderInLineWapper(
            startPoint: CGPoint(x: eggWapperRight.frame.midX, y:  (eggWapperRight.frame.maxY - 20)),
            endPoint: CGPoint(x: hat_red.frame.midX, y:  (hat_red.frame.minY - 10)),
            lineWidth: 20,
            rectColor: UIColor.init(red: 133/255, green: 240/255, blue: 240/255, alpha: 1),
            boderWidth: 8,
            boderColor: UIColor.init(red: 17/255, green: 152/255, blue: 148/255, alpha: 1)
        )
        
        // 中间横线
        for yPoint in [0.2, 0.4, 0.6, 0.8] as Array<CGFloat> {
            drawLineWithBoderInLineWapper(
                startPoint: CGPoint(x: eggWapperLeft.frame.midX, y:  (eggWapperLeft.frame.maxY + self.lineWapperHeight * yPoint)),
                endPoint: CGPoint(x: eggWapperRight.frame.midX, y:  (eggWapperRight.frame.maxY + self.lineWapperHeight * yPoint)),
                lineWidth: 20,
                rectColor: UIColor.init(red: 133/255, green: 240/255, blue: 240/255, alpha: 1),
                boderWidth: 8,
                boderColor: UIColor.init(red: 17/255, green: 152/255, blue: 148/255, alpha: 1)
            )
        }
    }
    
    func drawLineWithBoderInLineWapper(startPoint: CGPoint, endPoint: CGPoint, lineWidth: CGFloat, rectColor: UIColor, boderWidth: CGFloat, boderColor: UIColor) {
        let chickmarkLayer = CAShapeLayer()
        
        if(startPoint.y == endPoint.y){
            // 画横线
            chickmarkLayer.path = UIBezierPath(rect: CGRect(
                                                        x: startPoint.x + lineWidth/2,
                                                        y: startPoint.y - lineWidth/2,
                                                        width: endPoint.x - startPoint.x - lineWidth,
                                                        height: lineWidth
                                                        )).cgPath
            
        }else {
            // 画直线
            chickmarkLayer.path = UIBezierPath(roundedRect: CGRect(
                                                        x: startPoint.x - lineWidth/2,
                                                        y: startPoint.y,
                                                        width: lineWidth,
                                                        height: endPoint.y - startPoint.y
                                                        ), cornerRadius: lineWidth/2).cgPath
        }
        chickmarkLayer.fillColor = boderColor.cgColor
        
        
        let chickmarkLayer_inLine = CAShapeLayer()
        let boderInLineWidth = lineWidth - boderWidth
        if(startPoint.y == endPoint.y){
            // 画横线
            chickmarkLayer_inLine.path = UIBezierPath(rect: CGRect(
                                                        x: startPoint.x ,
                                                        y: startPoint.y - boderInLineWidth/2,
                                                        width: endPoint.x - startPoint.x + boderWidth / 2,
                                                        height: boderInLineWidth
                                                        )).cgPath
        } else {
            // 画直线
            chickmarkLayer_inLine.path = UIBezierPath(roundedRect: CGRect(
                                                        x: startPoint.x - boderInLineWidth/2,
                                                        y: startPoint.y + boderWidth/2  ,
                                                        width: boderInLineWidth,
                                                        height: (endPoint.y - startPoint.y - boderWidth)
                                                        ), cornerRadius: boderInLineWidth/2).cgPath
            
        }
        
        chickmarkLayer_inLine.fillColor = rectColor.cgColor
       
        lineWapper.layer.addSublayer(chickmarkLayer)
        lineWapper.layer.addSublayer(chickmarkLayer_inLine)
        
        let lastLineStartPoint = CGPoint(x: eggWapperLeft.frame.midX, y:  (eggWapperLeft.frame.maxY + self.lineWapperHeight * 0.8))
        if(lastLineStartPoint.equalTo(startPoint)){
            self.lastLineLayer = chickmarkLayer
            self.lastLineInLineLayer = chickmarkLayer_inLine
        }
    }
    
    func displayLastLine(_ isShow: Bool) {
        self.lastLineLayer?.isHidden = !isShow
        self.lastLineInLineLayer?.isHidden = !isShow
    }
    
    func displayCloud(_ isShow: Bool) -> UIViewPropertyAnimator{
        let cloudAni = UIViewPropertyAnimator(duration: isShow ? 0 : 1,curve: .linear, animations: {
            self.cloud.alpha = isShow ? 1 : 0
        })
        
        return cloudAni
    }

    fileprivate func getEggRunLineKeyFrameOptions(_ hasLastLine: Bool) -> Array<KeyFrameOptionItem>{
        var keyFrameOptions: Array<KeyFrameOptionItem> = []
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.0, translationX: 0, translationY: self.lineWapperHeight*0.2 + 50, rotated: 0, scaledX: 1.0))
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.1, translationX: self.lineWapperWidth, translationY:  self.lineWapperHeight*0.2 + 50, rotated: 0, scaledX: 1.0))
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.2, translationX: self.lineWapperWidth, translationY:  self.lineWapperHeight*0.4 + 50, rotated: 0, scaledX: 1.0))

        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.3, translationX: 0, translationY:  self.lineWapperHeight*0.4 + 50, rotated: 0, scaledX:  1.0))
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.4, translationX: 0, translationY: self.lineWapperHeight*0.6 + 50, rotated: 0, scaledX:  1.0))
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.5, translationX: self.lineWapperWidth,  translationY: self.lineWapperHeight*0.6 + 50, rotated: 0, scaledX: 1.0))
        keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.6, translationX: self.lineWapperWidth, translationY:  self.lineWapperHeight*0.8 + 50, rotated: 0, scaledX:  1.0))
        if(hasLastLine){
            // 走第四条线
            keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.7, translationX: 0, translationY:  self.lineWapperHeight*0.8 + 50, rotated: 0, scaledX: 1.0))
            keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.8, translationX: 0, translationY:  self.lineWapperHeight*1 + 10, rotated: 0, scaledX:  1.0))
        } else {
            
            keyFrameOptions.append(KeyFrameOptionItem(startTime: 0.7, translationX: self.lineWapperWidth, translationY:  self.lineWapperHeight*1 + 10, rotated: 0, scaledX:  1.0))
        }
        
        return keyFrameOptions
    }
    
    fileprivate func setChickKeyFrameOption() {
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.0, translationX: -17.0, translationY: 0.0, rotated: 10, scaledX: 1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.1, translationX: -33.0, translationY:  -10.0, rotated: -10, scaledX: 1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.2, translationX: -50.0, translationY:  0, rotated: 10, scaledX: 1.0))
        // 以上向左边跳到底後转身往回走
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.3, translationX: -33.0, translationY:  -10.0, rotated: -10, scaledX:  -1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.4, translationX: -27.0, translationY: 0.0, rotated: 10, scaledX:  -1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.5, translationX: 0.0,  translationY: -10.0, rotated: -10, scaledX: -1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.6, translationX: 30.0, translationY:  0.0, rotated: 10, scaledX:  -1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.7, translationX: 50.0, translationY:  -10.0, rotated: -10, scaledX: -1.0))
        //跳到最右边後 转身往原点走
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.8, translationX: 20.0, translationY:  0.0, rotated: 10, scaledX:  1.0))
        self.chickKeyFrameOptions.append(KeyFrameOptionItem(startTime: 0.9, translationX: 0.0, translationY:  -10.0, rotated: -10, scaledX: 1.0))
    }
    
    fileprivate func getChoose(_ sender: UIButton) -> String {
        if(sender == self.left_red){
            return "left_even"
        }
        if(sender == self.left_blue){
            return "left_odd"
        }
        if(sender == self.right_blue){
            return "right_odd"
        }
        if(sender == self.right_red){
            return "right_even"
        }
        
        return ""
    }
}

整个游戏撰写完成!

Kotlin 游戏示意

游戏 小鸡BB

Kotlin 游戏纪录

在Kotlin这边游戏纪录页面要如何取得纪录呢?
我们回到HistoryFragment.kt
新增 TextView 与 Rcycler View

游戏 小鸡BB
游戏 小鸡BB

  1. textView(TextView)
    属性 对齐 设定
    layout_width match_parent
    layout_height wrap_content
    background @color/gray_400
    paddingStart 5dp
    paddingTop 10dp
    paddingEnd 10dp
    paddingBottom 5dp
    text 游戏纪录
    textColor #424242
    textSize 20dp
    Start -> StartOf parent 0dp
    End -> EndOf parent 0dp
    Top -> TopOf parent 0dp
  2. recycler_view(RcyclerView)
    属性 对齐 设定
    layout_width 0d
    layout_height 0dp
    paddingStart 10dp
    paddingEnd 10dp
    scrollbars vertical
    layoutManager LinearLayoutManager
    Start -> StartOf parent 0dp
    End -> EndOf parent 0dp
    Top -> BottomOf textView 0dp
    Bottom -> BottomOf parent 0dp

设定好画面後, 就给RcyclerView设定一个资料来源
RcyclerView会接受一个Adapter
来处理每一个Cell资料显示的内容
而每个Cell也是一个 layout
所以我们先在res/layout底下新增一个 Layout Resource File
命名为 list_item.xml
内容这样

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/editTextBackground">

    <TextView
        android:id="@+id/item_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/selectableItemBackground"
        android:paddingTop="10dp"
        android:paddingBottom="10dp"
        android:textColor="@color/black"
        android:textSize="20sp" />
</LinearLayout>

然後在 com.test.chickbb 专案资料夹 底下新增一个package adapter
里面新增一个 kotlin class 叫 ItemAdapter

package com.test.chickbb.adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.test.chickbb.R
import com.test.chickbb.player.OrderHistory

class ItemAdapter(private val dataset: List<OrderHistory>): RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {
    private lateinit var mListener: onItemClickListener

    class ItemViewHolder(private val view: View, listener: onItemClickListener) : RecyclerView.ViewHolder(view), View.OnClickListener {
        // 取得 view
        val textView: TextView = view.findViewById(R.id.item_title)

        init {
            view.setOnClickListener {
                listener.onItemClick(adapterPosition)
            }
        }

        override fun onClick(v: View?) {
            TODO("Not yet implemented")
        }

    }
    // 定义点击事件 用 interface 由使用者实作
    interface onItemClickListener {
        fun onItemClick(position: Int)
    }

    fun setOnItemClickListener(listener: onItemClickListener){
        mListener = listener
    }

    // 设定使用的Layout
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
        // create a new view
        val adapterLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.list_item, parent, false)

        return ItemViewHolder(adapterLayout, mListener)
    }

    // 设定显示样式
    override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
        val item = dataset[position]
        holder.textView.text = "${position+1}. 结果${if (item.is_win) "赢 +" else "输"}${item.winPoint}"
    }

    // 设定有几个cell
    override fun getItemCount() = dataset.size
}


接下来我们预计点击Cell
会把游戏结果带到 ShowDetailFragment

首先到 fragment_show_detail.xml
设定layout 这边很单纯
就三个textView 而已
我直接用Code表示

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/frameLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?android:attr/colorPressedHighlight"
    tools:context=".ShowDetailFragment">

    <TextView
        android:id="@+id/detail_choose"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="20dp"
        android:text="选择:"
        android:textColor="#5D4037"
        android:textSize="20sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/detail_isWin"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="10dp"
        android:text="结果:"
        android:textColor="#5D4037"
        android:textSize="20sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/detail_choose" />

    <TextView
        android:id="@+id/detail_winPoint"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:layout_marginTop="10dp"
        android:text="得分:"
        android:textColor="#5D4037"
        android:textSize="20sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/detail_isWin" />

</androidx.constraintlayout.widget.ConstraintLayout>

再来我们到ShowDetailFragment.kt
设定会传入三个参数 用来显示详细结果

package com.test.chickbb

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.test.chickbb.databinding.FragmentShowDetailBinding

private const val ARG_PARAM1 = "choose"
private const val ARG_PARAM2 = "isWin"
private const val ARG_PARAM3 = "winPoint"

class ShowDetailFragment : Fragment() {
    private var _binding: FragmentShowDetailBinding? = null
    private val binding get() = _binding!!

    private var choose: String? = null
    private var isWin: Boolean? = null
    private var winPoint: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            choose = it.getString(ARG_PARAM1)
            isWin = it.getBoolean(ARG_PARAM2)
            winPoint = it.getString(ARG_PARAM3)
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentShowDetailBinding.inflate(inflater, container, false)
        binding.detailChoose.text =  binding.detailChoose.text.toString() + parseChoose(choose!!)
        binding.detailIsWin.text = binding.detailIsWin.text.toString() + if (isWin!!) "赢" else "输"
        binding.detailWinPoint.text = binding.detailWinPoint.text.toString() + winPoint
        return binding.root
    }

    companion object {
        @JvmStatic
        fun newInstance(param1: String, param2: Boolean, param3: String) =
            ShowDetailFragment().apply {
                arguments = Bundle().apply {
                    putString(ARG_PARAM1, param1)
                    putBoolean(ARG_PARAM2, param2)
                    putString(ARG_PARAM3, param3)
                }
            }
    }

    fun parseChoose(chooseKey: String):String {
        var choose = ""

        when (chooseKey) {
            "left_odd" -> choose = "左蓝"
            "left_even" -> choose = "左红"
            "right_odd" -> choose = "左蓝"
            "right_even" -> choose = "左蓝"
        }

        return choose
    }
}

接下来就要设定导航了
回到 nav_graph
对 showDetailFragment 设定这三个参数

我们回到HistoryFragment.kt

package com.test.chickbb

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import com.test.chickbb.adapter.ItemAdapter
import com.test.chickbb.databinding.FragmentHistoryBinding
import com.test.chickbb.player.OrderHistory
import com.test.chickbb.player.PlayerViewModel

class HistoryFragment : Fragment() {
    private var _binding: FragmentHistoryBinding? = null
    private val binding get() = _binding!!
    // lateinit 为延後初始化
    private lateinit var player: PlayerViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        _binding = FragmentHistoryBinding.inflate(inflater, container, false)
        val recyclerView = binding.recyclerView
        // 从Activity取得共用的ViewModel
        player = ViewModelProvider(requireActivity()).get(PlayerViewModel::class.java)

        // 先确认里面有资料
        if(!player.history.isNullOrEmpty()) {
            // 转成 history List
            val listHistorys = player.history?.toList()!!
            // 产生出 Adapter 实体
            val adapter = ItemAdapter(listHistorys)
            // 将 Adapter 设定给 RecyclerView
            recyclerView.adapter = adapter

            // 实作 点击时要做的事
            adapter.setOnItemClickListener(object : ItemAdapter.onItemClickListener{
                override fun onItemClick(position: Int) {
                    // 被点到的对象
                    val currentOrder = listHistorys[position]
                    // 使用 Directions跳转页面
                    val action = HistoryFragmentDirections.actionHistoryFragmentToShowDetailFragment(currentOrder.choose, currentOrder.is_win, currentOrder.winPoint.toString())
                    // 跳页搂
                    binding.root.findNavController().navigate(action)
                }

            })

            recyclerView.setHasFixedSize(true)
        }


        return binding.root
    }

    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}

好了~所有功能完成
呼~

差异

在Swift内 页面与页面间互传资料
语法上比较简单

但对於程序设计来说 用 as? 某个页面Class这种方式
感觉比较随意 个人不太喜欢

而Kotlin这边采用 ViewModelProvider 可以取得单一实例的Service
这样方法个人更喜欢一点
而Kotlin 这边采用的MVVM架构

我也觉得很好用喔

小碎嘴时间 ヽ(゚´Д`)ノ゚

嗯嗯...程序码好多 头好炸
本来也想把Kotlin的完整程序贴出来

但仔细看了看好像不是很必要
头脑炸裂的程序开发结束了!

明天开始来准备上架搂~


<<:  Day19 Plugin 从零开始到上架 - 取得授权码(Android)

>>:  Day 29:专案07 - 天气小助理03 | Heroku云端平台

DVWA练习-Cross Site Scripting

还在学校上课的时候 某堂课的老师跟我们分享他在准备CEH的考试 老师示范了Cross Site Sc...

Day 23 - 开发人员工具的日常

前言 今天再来聊聊另一个重要的工具,是很多人刚开始学 Javascript 就一路接触到现在,如果没...

[Android Studio 30天自我挑战] 透过Banner来轮播广告资讯

现在常常在手机或是电脑网页中会在页面中的一部分会看到不同的广告, 这篇我们运用banner在画面中显...

Day 11: Amazon GuardDuty简介

What is Amazon GuardDuty? GuardDuty可以帮助您监控VPC Flow...

DAY8 Linebot 自动回应-1

设定完成後,开启Django应用程序(APP)的views.py档案,这边就是撰写LINE Bot接...