一、题目描述
题目:“福建赌王”之争
【题目背景】
话说,自称“赌王”的老周与同样自称“赌王”的老刘在福州展开“赌王”名号的争夺。两人商议决定使用福建当地的一套纸牌游戏规则进行博弈,即“福建十三水”。约定三周后展开决战。老刘修习代码多年,希望开发一套自动化的出牌系统,具体游戏规则请上网查询或找福大柯老板,本次作业要求提交一份设计好的原型设计图。
WARNING:珍惜钱财,远离赌博(含AI赌博)。
二、相关的链接
我的Github地址 详情
结对同学的Github地址 详情
结对同学博客地址 详情
上次原型设计的博客链接 详情
三、具体的分工
我在本次十三水作业中主要负责客户端(iOS)的编写,队友陈展鸿负责后端算法的实现。
注:由于iOS的特殊性,暂无法打包成二进制文件,若负责测评前端的同学没有运行iOS的工具,请联系我进行测评。
四、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | · 计划 | 30 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 999 | 1000 |
Development | · 开发 | 120 | 180 |
· Analysis | ·需求分析(包括学习新技术) | 100 | 500 |
· Design Spec | · 生成设计文档 | 10 | 20 |
· Design Review | · 设计复审 | 5 | 5 |
· Coding Standard | · 代码规范(为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 20 | 15 |
· Coding | · 具体编码 | 100 | 100 |
· Code Review | · 代码复审 | 15 | 10 |
· Test | · 测试(自我测试,修改代码,提交修改) | 20 | 10 |
Reporting | · 报告 | 20 | 15 |
· Test Repor | · 测试报告 | 10 | 10 |
· Size Measurement | · 计算工作量 | 5 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结,并提出过程改进计划 | 10 | 20 |
·合计 | 1464 | 1935 |
五、解题思路与设计实现说明
- iOS端整体代码逻辑主要采用MVC设计模式。
- iOS端网络接口的使用:使用了Alamofire对后端算法的服务器跟福哥的评测服务器进行访问,SwiftyJSON对JSON数据进行解析处理。
- iOS端网络请求的代码使用了单例模式,下面给出部分代码示例如下:
class HubRequest:NSObject{ static let shared = HubRequest() open var HubData = HubDataModel() //登录请求接口 func loginRequest(userName:String,passWord:String, _ completion: @escaping (HubDataModel) -> ()){ let parms = [ "username": userName, "password": passWord ] let header:HTTPHeaders = ["Content-Type":"application/json"] Alamofire.request("https://api.shisanshui.rtxux.xyz/auth/login", method: .post, parameters: parms, encoding: JSONEncoding.default, headers: header).responseJSON { (response) in if response.result.isSuccess { if let jsons = response.result.value{ let jsonDict = JSON(jsons) let jsonData = jsonDict["data"] guard jsonData.count > 0 else{ return } self.HubData.token = jsonData["token"].stringValue self.HubData.userId = jsonData["user_id"].intValue } completion(self.HubData) } else { print("登录失败:\(response)") } } } //调用HubRequest单例发送登录请求 @IBAction func login(_ sender: Any) { NetWorkActivityIndicatorView.startAnimating() HubRequest.shared.loginRequest(userName: userText.text ?? "", passWord: passWord.text ?? "") { (HubData) in self.configureMenuView() self.NetWorkActivityIndicatorView.stopAnimating() } }
后端算法的关键流程图
后端算法内部类图
iOS端内部类图
六、关键代码的解释
class AiPlayRequest: NSObject{ //创建后端与对战平台的请求单例 static let shared = AiPlayRequest() var sortedCard = SortedCardModel() //从后端拿到要出牌的顺序 func getSortedCards(id: Int, card: String, _ completion: @escaping (SortedCardModel) -> ()){ let parms = [ "status":0, "id":id, "card":card ] as [String : Any] Alamofire.request("http://49.235.150.59:8080/thirteenWaterByCzh/getCards", method: .post, parameters: parms).responseJSON { (response) in if response.result.isSuccess{ print(response) if let jsons = response.result.value{ let jsonDict = JSON(jsons) let cards = jsonDict["card"] self.sortedCard.id = id self.sortedCard.first = cards[0].stringValue self.sortedCard.second = cards[1].stringValue self.sortedCard.third = cards[2].stringValue } completion(self.sortedCard) }else{ print("sorting cards error:\(response)") } } } //向对战平台提交牌 func submitRequest(id:Int){ let header = [ "Content-Type":"application/json", "X-Auth-Token":HubRequest.shared.HubData.token ] let parms = [ "id":id, "card":[ sortedCard.first, sortedCard.second, sortedCard.third ] ] as [String : Any] Alamofire.request("https://api.shisanshui.rtxux.xyz/game/submit", method: .post, parameters: parms, encoding: JSONEncoding.default, headers: header).responseJSON { (response) in if response.result.isSuccess{ print("出牌成功 \(response)") }else{ print("出牌失败 \(response)") } } } }
//实际调用中对返回对字符串解析并与卡牌图片进行匹配 AiPlayRequest.shared.getSortedCards(id: hubData.id, card: hubData.card) { (sortedCards) in let firstCards = sortedCards.first.split(separator: " ") self.afPoker1.image = UIImage(named: toImageName(str: String(firstCards[0]))) self.afPoker2.image = UIImage(named: toImageName(str: String(firstCards[1]))) self.afPoker3.image = UIImage(named: toImageName(str: String(firstCards[2]))) let secondCards = sortedCards.second.split(separator: " ") self.afPoker4.image = UIImage(named: toImageName(str: String(secondCards[0]))) self.afPoker5.image = UIImage(named: toImageName(str: String(secondCards[1]))) self.afPoker6.image = UIImage(named: toImageName(str: String(secondCards[2]))) self.afPoker7.image = UIImage(named: toImageName(str: String(secondCards[3]))) self.afPoker8.image = UIImage(named: toImageName(str: String(secondCards[4]))) let thirdCards = sortedCards.third.split(separator: " ") self.afPoker9.image = UIImage(named: toImageName(str: String(thirdCards[0]))) self.afPoker10.image = UIImage(named: toImageName(str: String(thirdCards[1]))) self.afPoker11.image = UIImage(named: toImageName(str: String(thirdCards[2]))) self.afPoker12.image = UIImage(named: toImageName(str: String(thirdCards[3]))) self.afPoker13.image = UIImage(named: toImageName(str: String(thirdCards[4]))) AiPlayRequest.shared.submitRequest(id: self.hubData.id) }
七、性能的分析与改进
可以看到主要进行出牌算法占据了绝大部分时间,小部分时间由io、依赖包函数调用占用。
- 可以先直接判断是否为特殊牌型进而直接输出
- 设置一定的权重阈值 达到即可输出
不同的散排牌型放置不同的数组内
八、 单元测试
iOS前端的连接请求测试
- 登录测试:
- 开始战局测试:
- 排行榜测试:
- 历史战绩测试:
历史战绩详情测试:
后端算法单元测试
九、 github迁入记录
十、遇到的代码模块异常:
- 问题描述:因为给的工期较短,为了同学测评时能得到更好的效果,方便后期进行屏幕适配并且提高开发效率,此次iOS端开发过程中大量使用了xib来编写界面。但也因此踩了一些较为弱智的坑。印象比较深刻的是一次由于view结构没有设计好导致点击tableView的时候所有cell都会消失,将其视为玄学debug挺久。
- 做过哪些尝试:不论是重新使用xib写一次还是用纯代码再撸一遍都解决不了这个问题,后面意识到可能是自己设计的view结构产生了冲突,便重新改了一下逻辑结构。
- 是否解决:已解决
- 有何收获:emmmmm感觉自己越来越菜了
十一、 我的队友czh大佬牛逼的一批!后端都不用我掺合,直接后端算法部署到服务器之后把接口丢给我就完事了!
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 重要成长 |
---|---|---|---|---|
第一周 | 0 | 0 | 5 | 学习原型设计(墨刀)的使用 |
第二周 | 0 | 0 | 20 | 学习iOS端开发的基本知识 |
第三周 | 3000 | 3000 | 50 | 进行iOS开发 |
第四周 | 2000 | 5000 | 20 | 进行iOS端逻辑优化 |