https://github.com/uryyy16/Card-game
姓名 | 博客链接 | 具体分工 |
---|---|---|
黄慧卿 | https://www.cnblogs.com/2636r/p/15440455.html | 1.设计原型 2.编写网络接口 3.编写界面 4.研究托管算法 |
刘璐瑀 | https://www.cnblogs.com/Es-war/p/15452317.html | 1.设计原型 2.编写 js 代码 3.研究托管算法 |
一、原型设计
https://modao.cc/app/a4c1256fc26a4af04f45bd73e153fec069099124
1.说明你所采用的原型开发工具
采用的原型工具:MockingBot
-
登录界面:用户输入账号密码,若账号密码正确,点击“确定”后可跳转到游戏模式选择界面;不正确,则弹出提示信息,告知用户“账号或密码错误”。
-
游戏模式选择界面:包括三种模式:人人对战,在线对战,人机对战。若选择人人对战和人机对战,则直接进入游戏界面;若选择在线对战,则进入创建或加入房间。
-
创建或加入房间界面:包括两个选择:创建房间、加入房间。若选择创建房间,则跳转至创建房间界面;若选择加入房间界面,则跳转至加入加入房间界面
-
创建房间界面:创建成功后,剪切板自动复制该对局邀请码,点击“确定”后跳转至游戏界面。
-
加入房间界面:输入正确邀请码,进入该房间,跳转至游戏界面。
-
游戏界面:三个界面基本相同,除了在线对战界面增加托管功能。
-
游戏结束界面:显示游戏结果。
2.遇到的困难及解决方法
- 困难描述
- 之前从未接触过原型设计,由于这次是卡牌游戏,所以很自然地联想到了斗地主,想要模仿它的设计。我们两个人都不会PS抠图,最开始的时候,手动截取了52张不同的扑克,想要借助QQ的截图工具来截扑克牌,但是这样麻烦不说,图片大小尺寸也不一致,后期定位起来十分麻烦。
- 不知道要设计出什么风格的界面。
- 设计游戏界面时,起初的想法是将所有的牌都展示出来,受限于手机屏幕的大小,这样设计会显得较为杂乱。
- 解决过程
- 后来有同学提醒我们说可以在淘宝买斗地主素材,于是斥巨资5元购入一堆斗地主素材。
- 游戏名为“猪尾巴”,而小猪佩奇动画的画风简单、明丽、可爱,决定选用小猪佩奇的图片当做界面的背景,界面输入框、按钮等均设置为透明。
- 简化游戏界面,用一张扑克背面来表示卡组,卡池区只显示当前顶部花色,玩家手牌均只显示四种不同花色的牌及其数量,而不是具体展示出花色及数字。
- 有何收获
- 淘宝是万能的。
- 初步学习到了原型设计相关的过程、步骤。
- 多借鉴市面上设计精美的软件、小程序。
- 一个清晰的原型设计可以为之后的实现打下良好的基础。
二、原型设计实现
1.代码实现思路
-
网络接口的使用
- 登录接口:将输入的账号密码数据通过该接口发送给服务器,请求成功后,获取 token,存入全局变量中,用于后续请求接口。
- 创建对局接口:当用户选择创建房间时,发送请求至服务器,请求成功后获取相应的 uuid 作为邀请码,将此 uuid 存入全局变量中,用于后续游戏进行时向服务器发送具体操作,并可将此邀请码发送给想要邀请的用户,便于之后加入同一房间。
- 加入房间接口:用户输入相应的邀请码后,将此邀请码作为其 uuid 存入全局变量中,用于后续游戏进行时向服务器发送具体操作。
- 执行玩家操作接口:抽取手牌时,需告知服务器具体花色与数字;抽取卡组时,需向服务器发送请求,根据返回的参数获取所抽取牌的具体信息。
- 获取上步操作接口:向服务器发送请求,询问当前是谁的回合,若为己方回合,根据返回信息,在本地对对方的上步操作进行及时更新处理,然后监听己方的具体操组,并通过“执行玩家操作接口”向服务器发送对应请求。
- 获取对局信息接口:对局结束后,向服务器发送请求,根据返回的信息获取赢家信息,并记录到全局变量 winner 当中,根据房主及返回数据综合判断该对局赢家。
-
代码组织与内部实现设计(类图)
-
登录界面函数
- inputName():绑定用户账号输入函数
- inputPWD():绑定用户密码输入函数
- login():登录函数
-
模式选择界面函数
- choose_1():绑定用户选择人人模式函数
- choose_2():绑定用户选择在线模式函数
- choose_3():绑定用户选择人机模式函数
-
选择创建或加入房间界面函数:
- createRoom(): 绑定用户选择创建房间函数
- joinRoom():绑定用户选择加入房间函数
-
创建房间界面函数
- joingame():绑定用户选择创建房间函数
- onLoad():监听页面加载函数:发送请求给服务器端
-
加入房间界面函数
- inputInfo():拿到用户输入信息函数
- bindbtn():绑定用户点击“加入房间”按钮函数
-
游戏界面函数
- init()函数:初始化卡牌
- judge_type()函数:判断所抽取的牌花色与卡池区花色是否一致
- add_pool()函数:将所抽取的牌放入卡池区
- take_from_inside()函数:玩家从手牌中抽取
- take_from_outside()函数:玩家从卡组中抽取
- judge_game_over()函数:判断对局是否结束
- player()函数:绑定玩家点击卡牌事件函数
- get_poke()函数:绑定玩家的点击卡组事件
- watch():观察者
- judge_type()函数:判断所抽取的牌花色与卡池区花色是否一致
- tell_0()函数:告知服务器抽取卡组操作
- tell_1()函数:告知服务器抽取手牌操作
- AI_robot()函数:托管函数
- change_robot()函数:切换托管
- get_info()函数:结束时获取对局信息
- get_last()函数:获取上步操作
- onLoad()函数:加载页面
- onUnload()函数:卸载页面函数
-
游戏结束界面:
- onLoad()函数:监听页面加载函数,根据全局变量winner渲染界面
- onShow()函数:监听页面显示界面,根据全局变量winner渲染界面
-
-
说明算法的关键与关键实现部分流程图
三种模式的逻辑基本一致,在线对战在实现上略有不同,下面以在线对战为准,说明实现思路
-
贴出你认为重要的/有价值的代码片段,并解释(2分)
//获取上步操作
get_last: function(){
var op_ty
current_player = 1
var that = this
this.get_info()
this.data.interval = setInterval(function () {
console.log('在线对战循环')
current_player = 1
wx.request({
url: 'http://172.17.173.97:9000/api/game/' + app.globalData.uuid + '/last',
method: "GET",
header: {
"Authorization": wx.getStorageSync('token')
},
data: {
},
success: res => {
console.log(res)
if(res.data.code == 400){
clearInterval(that.data.interval)
that.get_info()
//that.judge_winner()
}
if(res.data.data.your_turn == true){
current_player = 0
clearInterval(that.data.interval)
console.log('为我的轮次')
that.setData({
msg: '己方回合'
})
//记录上步操作
var info = res.data.data.last_code
console.log('拿到对方的操作为:' + info)
if (info.length != 0){
current_player = 1
tmp_ty = info[4]
tmp_num = info[5]
op_ty = info[2]
console.log("拿到对方操作")
console.log(op_ty)
console.log(tmp_ty)
console.log(tmp_num)
if (info.length == 7) tmp_num += info[6]
if (op_ty == '0') that.take_from_outside()
else that.take_from_inside(1)
}
}
else{
that.setData({
msg: '对方回合'
})
}
},
fail: res => {
console.log("123456" )
console.log(res)
}
})
},1000)
//this.get_info()
console.log('退出循环')
//记录玩家1的操作
current_player = 0;
if (deposit_0 == 1) this.AI_robot()
},
watch: { //观察者
done: function(newValue,oldValue){
if(newValue){ //done为true,本回合结束
this.data.done = false; //开启下一回合
this.get_last(); //开启下一回合
}
},
robot: function(newValue,oldValue){ //观察是否在托管状态
if(newValue){
this.AI_robot();
}
}
},
调用get_last()函数获取上步操作以及判断当前为谁的轮次,并与 watch()函数搭配使用,解决线程卡死问题。
-
性能分析与改进
有部分冗余代码,托管函数不够智能 -
描述你改进的思路
1.通过数据路径的写法,来将数据分批的传输到视图层中,减少一次性setData的数据大小
2.减少setData的数据量
3.合并setData的请求,减少通讯的次数
2.压缩代码,清理无用的代码
3.采用分包策略
4.手动的“清理”后台界面 -
展示性能分析图和程序中消耗最大的函数
程序中消耗最大的函数:get_last()
不断向后端发送请求(每隔一秒),获取上步操作,直到当前回合为己方轮次,由于网络质量较差,耗时较长。 -
展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路
这次没有引入后端,全是在小程序的 js 文件中,通过 JavaScript 语言来实现游戏逻辑,所以并没有编写单元测试代码。代码的正确性在对战中检测,每次对局中观察具体出牌情况及处理的正误。
2.Github使用相关
-
README
-
.gitignore
-
使用分支管理提交代码,使用pull request
-
开源协议
-
Issues模板
-
贴出Github的代码签入记录,合理记录commit信息
3.遇到的代码模块异常或结对困难及解决方法
-
困难描述
- 接口使用混乱,经常出 bug。
- web 可以为每个鼠标点击事件设置一个独立的线程,一开始以为小程序也可以支持这样的操作,但是在在线对战中发现一旦运行 js 代码,无法监听到点击事件,输出进程 id 后发现原来是进程在循环体那部分上卡死了,虽然代码逻辑上没有错误,但是无法响应点击事件,就永远无法跳出循环;不使用循环,又不知道如何不断监听、记录玩家行为。
-
解决过程
- 认真研究各个接口,厘清各接口的正确使用方法、需要传递的参数,分析其对该游戏实现上的作用。
- 了解到小程序可以引入“观察者”,一旦其所观察的值发生变化,可以立刻执行其内部逻辑代码。弃用循环体,引入观察者,不断监听具体数据的变化,以达到原先循环体的效果,并且不会导致进程的卡死。
-
有何收获
- 遇事不决,多问同学。
- 学会了如何使用“观察者”。
- 掌握了接口的使用方法。
4.评价你的队友
- hhq
- 值得学习的地方
- 乐观向上的精神
- 赶ddl的强大心脏
- 需要改进的地方
- 下次别赶 ddl 了
- 值得学习的地方
- lly
- 值得学习的地方
- 无惧挑战的精神
- 极限编程的魄力
- 需要改进的地方
- 下次别再一起当 ddl 战士了
- 值得学习的地方
4.提供此次结对作业的PSP和学习进度条(每周追加)
- 学习进度条(一周极限编程)
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 3736 | 3736 | 54.3 | 54.3 | 熟悉微信小程序的相关语法,学习JavaScript、“观察者”的使用 |
- PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 60 | 120 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 540 | 600 |
· Design Spec | · 生成设计文档 | 40 | 30 |
· Design Review | · 设计复审 | 60 | 120 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 10 |
· Design | · 具体设计 | 300 | 600 |
· Coding | · 具体编码 | 1000 | 1400 |
· Code Review | · 代码复审 | 60 | 40 |
· Test | · 测试(自我测试,修改代码,提交修改) | 200 | 290 |
Reporting | 报告 | ||
· Test Repor | · 测试报告 | 30 | 10 |
· Size Measurement | · 计算工作量 | 15 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
· 合计 | 2290 | 3260 |
三、心得
- hhq
- 前期觉得一个月时间绰绰有余,一直都是慢慢悠悠的状态。摆烂到最后一周才开始写这次作业,所有人都觉得我们两个这次真的写不完了。每天都是不断赶进度,最近一周的口头禅就是“我们要死了”,“我们可以活”。刚开始设计原型时,一顿乱搞之后又推翻重来,各种手忙脚乱。之前虽然写过 web 界面,但是不涉及到和后端的交互,因此查看接口文档时,也是一头雾水,再加上自己理解上的偏差,导致接口使用混乱,询问同学之后,才逐步弄明白究竟如何正确发送请求。但是服务器依托于校园网,校园网质量不佳,有时候会导致一些莫名其妙的 bug。之前使用 GitHub 时,都只是简单地使用一下上传功能,由于这次作业的硬性要求,极速学习了一些 GitHub 相关操作,进一步加深了对其的理解。这次结对编程结束之后,紧接着就是团队编程作业,希望这次好好规划,尽早完成自己地工作,不要再赶 ddl。
- lly
- ddl果然是第一生产力,此次为期一个月的编程作业,硬是拖成一周极限编程。知道我们这周才开始写作业的同学,基本上都觉得我们要完蛋了,每天都是“死去活来”的,一下觉得“我们可以活”,一下觉得“我们死定了”,如此反复循环。遇到了各种各样奇奇怪怪的 bug,小程序不像 web,天生地支持多线程,在运行其他代码时也可以响应鼠标点击事件。一开始由于线程卡死,我们所编写的小程序无法响应鼠标点击事件,多亏了同学提醒,才知道可以引入“观察者”来处理,跳出死循环。这次作业的过程中,我们两人基本都是一起完成的,时间太赶,再加上不知道如何使用后端来实现一些算法逻辑,所以将逻辑实现这部分全部放入 js 中,可能是不熟悉 js 吧,总是有些意想不到的 bug,关键这些 bug 还不是因为逻辑错误引起的,也是够玄学的,只能说“不要靠近,会变得不幸”。经历了这一周极限编程,身心俱疲,希望下次尽早规划,不要再当 ddl 战士。
- 特别鸣谢
感谢郑浩彬,陈志良,周浩东三位同学的热心帮助