姓名 | 具体分工 |
---|---|
李晓芳 | 原型设计,游戏逻辑,AI算法 |
江舒颖 | 原型设计,前端界面,接口连接 |
一、原型设计
设计说明
-
设计逻辑:
-
开发工具: 墨刀(MockingBot)
-
原型介绍:
进入小程序将会进入启动页,点击进入游戏会显示选择游戏模式的界面
选择人机模式或者人人模式将不需要登陆,可直接进行游戏(以下以此为人人对战页面、人人对战带有托管页面、人机对战页面)
选择在线模式时,如果未登录,将会需要登录,登录后可自己创建房间或者加入其他人已经组好的对局,如果已经登陆则可以直接查询对局
游戏结束时将会有一个结束页面显示输赢情况,之后会有弹窗让你选择继续下一局返回主页面或者退出(以下依次为结束页面、退出登录页面)
遇到的困难及解决方法
- 困难描述: 原型的保真度选择问题。低保真原型设计耗时短,易于理解,便于修改。高保真原型交互性强,有利于后续原型的实现,对团队协作更为友好,但设计耗时长,修改成本高。
- 解决过程: 综合考虑高保真度和低保真度的优缺点,最理想的解决方案是:前期采用低保真原型,基本功能实现的中期采用高保真原型。再考虑到了时间的因素,我们制定了最后的计划:在结对编程的第一周时间内,尽可能地完善原型设计,核心关键部分必须制作高保真的原型,其他部分至少做出低保真原型。
- 收获: 通过所见即所得的制作原型的形式,我们对页面跳转的交互设计方法有了更为深刻的理解。同时,在实际选择原型保真度的过程中,我们两人进行得到了初步的磨合,这为后期的原型设计实现带来方便。
二、原型设计实现
代码实现思路
- 网络接口的使用
- 登录接口一开始使用
'content-type':'application/json'
一直显示参数错误,后来查了一些资料把它改成了'content-type': 'application/x-www-form-urlencoded'
就可以成功接入了。 - 除了登录接口,所以接口都需要带
'Authorization':token
。 - 一些接口存在频繁使用,对于这个问题,我将其封装为函数,减少代码的冗余度
/*获取上步操作*/
getLast : function(e){
const token=wx.getStorageSync("token");
wx.request({
url: 'http://172.17.173.97:9000/api/game/'+e+'/last',
data: {},
header: {'content-type':'application/json',
'Authorization':token},
method: 'GET',
success: (res) => {
console.log(res.data)
if(res.data.code==200){
wx.redirectTo({
url: '/pages/RandRgames/RandRgames'
});
}
},
fail: () => {},
complete: () => {}
});
}
-
代码组织与内部实现设计
-
生命周期函数
onLoad 可获取来自其它页面的数据
onShow 实现一进入页面就要开始的操作,例如页面初始化 -
页面渲染函数
imageUrl 实现不同牌的图片转换
changeview 实现托管页面的改变
iflook 实现卡组(即记牌功能)的显示与隐藏 -
对局函数
clickscard、clickhcard、clickccard、clickdcard 分别对应Player1对于四种花色牌的响应
clickscard1、clickhcard1、clickccard1、clickdcard1 分别对应Player2对于四种花色牌的响应
Shuffle 洗牌操作
SetCard 设置卡组初值
SelectGroup 卡组抽牌
OpHand 处理手牌
SelectHand 手牌出牌
IsEat 吃牌判断
EatHand 手牌吃牌
IsWinner 胜利者判断
Update 更新页面数据 -
说明算法的关键与关键实现部分流程图
人人对战、人机对战、在线对战都以人人对战的游戏逻辑为基础展开。而游戏逻辑算法的关键在于手牌出牌、卡组抽牌这两个地方,在玩家完成操作后,更新手牌、卡组和放置区。
-
贴出重要的/有价值的代码片段,并解释
是对人人对战游戏逻辑的基本实现,首先是洗牌,之后等待点击事件的发生,根据玩家选择,进行卡组抽牌、手牌出牌不同的操作。值得注意的是,卡组抽牌、手牌出牌需要更新手牌、卡组和放置区的卡牌,并对不同花色进行归类。
Shuffle: function(){ // 洗牌
var that = this;
var times = 5; // 默认洗牌5次
for(var i = 0; i < times; i++){
for(var j = 0; j < 52; j++) {
var rvalue = Math.round(Math.random()*50+1); // 随机生成1~51的一个整数
var tempcard = that.data.onecard[j];
that.data.onecard[j] = that.data.onecard[rvalue];
that.data.onecard[rvalue] = tempcard;
}
}
},
SelectGroup: function(e){ // 卡组抽牌
var that = this;
that.data.placement_card[that.data.placement_num++] = that.data.group_card[that.data.group_num--];
if(this.IsEat() == true){
this.EatHand();
}
this.Update();
if(that.data.group_num == -1){ // 卡组为空,结束对局
that.data.finished = true;
this.IsWinner();
}
},
OpHand: function(e){ // 处理手牌
var that = this;
var suit = that.data.card.substring(0, 1);
var i = parseInt(that.data.turn);
that.data.player[i].total++;
switch (suit) { // 对应花色
case "S": // 黑桃S
that.data.player[i].hand[0][that.data.player[i].num[0]] = that.data.card;
that.data.player[i].num[0]++;
break;
case "H": // 红桃H
that.data.player[i].hand[1][that.data.player[i].num[1]] = that.data.card;
that.data.player[i].num[1]++;
break;
case "C": // 梅花C
that.data.player[i].hand[2][that.data.player[i].num[2]] = that.data.card;
that.data.player[i].num[2]++;
break;
case "D": // 方块D
that.data.player[i].hand[3][that.data.player[i].num[3]] = that.data.card;
that.data.player[i].num[3]++;
break;
}
},
SelectHand: function(e){ // 手牌出牌
var that = this;
var suit = that.data.card.substring(0, 1);
var i = parseInt(that.data.turn);
if(that.data.player[i].total != 0){
switch(suit) { // 对应花色
case "S": //黑桃S
if(that.data.player[i].num[0] == 0){
break;
}
that.data.player[i].total--;
that.data.placement_card[that.data.placement_num++] = that.data.player[i].hand[0][--that.data.player[i].num[0]];
break;
case "H": //红桃H
if(that.data.player[i].num[1] == 0){
break;
}
that.data.player[i].total--;
that.data.placement_card[that.data.placement_num++] = that.data.player[i].hand[1][--that.data.player[i].num[1]];
break;
case "C": //梅花C
if(that.data.player[i].num[2] == 0){
break;
}
that.data.player[i].total--;
that.data.placement_card[that.data.placement_num++] = that.data.player[i].hand[2][--that.data.player[i].num[2]];
break;
case "D": //方块D
if(that.data.player[i].num[3] == 0){
break;
}
that.data.player[i].total--;
that.data.placement_card[that.data.placement_num++] = that.data.player[i].hand[3][--that.data.player[i].num[3]];
break;
}
if(this.IsEat() == true){
this.EatHand();
}
}
this.Update();
},
IsEat: function(e){// 吃牌判断
var that = this;
if(that.data.placement_num <= 1){
return false;
}
var str1 = that.data.placement_card[that.data.placement_num-1].substring(0, 1);
var str2 = that.data.placement_card[that.data.placement_num-2].substring(0, 1);
if(str1 == str2){ // 花色相同
return true;
}
return false;
},
EatHand: function(e){// 手牌吃牌
var that = this;
for(var i = 0; i < that.data.placement_num; i++){
that.data.card = that.data.placement_card[i];
this.OpHand();
}
that.data.placement_num = 0;
},
IsWinner: function(e){// 胜利者判断
var that = this;
if(that.data.player[0].total < that.data.player[1].total){
that.data.winner = "0";
}else{
that.data.winner = "1";
}
this.setData({
image_url0:'https://i.loli.net/2021/10/24/hFNM9dbDB1wCnak.png'
})
wx.navigateTo({
url: '/pages/over/over?id=that.data.winner'
});
},
-
性能分析与改进
性能上,小程序的图片加载较慢,影响了对游戏玩法的体验感。通过去除冗余的界面,完善了界面逻辑,在这方面有了一定的改进。
游戏逻辑方面,各个类的封装性做得不是很好,出现过相互扰乱的问题。游戏的反应不是很灵敏。 -
描述改进的思路
兼并人人对战、人机对战、在线对战公用的函数;不需要页面响应的数据不放在data中初始化;避免频繁触发的事件重度内存操作。尽可能减少小程序对运行内存的消耗。
去除不必要的冗余界面,完善界面交互的逻辑。 -
展示性能分析图和程序中消耗最大的函数
消耗最大的函数应该是setDate函数。 -
展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路
测试的是生命周期函数--监听页面加载onLoad()。
比对游戏参数在接口的返回和本地的记录是否一致,不一致的话查看出错的地方。信息通过 console.log()输出。
wx.request({
url: 'http://172.17.173.97:9000/api/game/?uuid',
header: {'content-type':'application/json',
'Authorization':token},
method: 'GET',
success: (res) => {
// 核验信息是否一致
console.log(res.data);
console.log(that.data.player[0].hand);
console.log(that.data.player[1].hand);
if(res.data.winner!=that.data.winner){
console.log("error");
}
},
fail: () => {},
complete: () => {}
});
贴出Github的代码签入记录,合理记录commit信息
遇到的代码模块异常或结对困难及解决方法
- 困难描述: 小程序开发过程中,出现白屏问题。可能是运行内存过大、接口API超时。
- 解决过程: 首先,减少小程序对运行内存的消耗。一方面,不需要页面响应的数据不放在data中初始化。另一方面,避免频繁触发的事件重度内存操作,比如上拉加载,下拉刷新时间增加节流。其次是,监督接口API是否超时。
- 收获: 最大的收获是一个bug满满的小程序。但其实在制作小程序的过程中,我们已经解决了诸如小程序出现白屏等很多问题,这是一个成长的过程。
评价你的队友
李晓芳:
- 队友值得学习的地方:
- 会主动督促项目进行,常常是push我学习和打代码。
- 有不错的前端编程基础,协作起来轻松愉快。
- 编程有一些需要交接的地方,例如参数传递、文件做出了改动,总是说得很清楚。
- 队友需要改进的地方:
- 应该也是ddl驱动型队友,虽然我们一起扛ddl,但是交作业的时候太焦头烂额了。
- 注释跟空格强迫症看了有一点难受,希望能做改进。
江舒颖:
- 队友值得学习的地方:
- 在做事情前会先把思路理清,便于更好地展开工作。
- 善于把握大方向,会以整体为重,不会因小失大。
- 当不能把每个地方都做得很好时,能够比较准确地抓住比较重要的地方先做。
- 代码规范性很好,习惯于做注释,可读性强
- 队友需要改进的地方:
- ddl驱动型队友,觉得可以更早一点开始完成作业,可以完成得更好。
- 沟通仍需加强,关于一些功能、接口参数在我们俩之间存在理解偏差,导致在编写代码过程中有些工作会突然没法进行。
PSP和学习进度条
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 50 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 50 |
Development | 开发 | 930 | 1310 |
· Analysis | · 需求分析 (包括学习新技术) | 100 | 180 |
· Design Spec | · 生成设计文档 | 20 | 30 |
· Design Review | · 设计复审 | 90 | 100 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
· Design | · 具体设计 | 120 | 150 |
· Coding | · 具体编码 | 360 | 600 |
· Code Review | · 代码复审 | 120 | 100 |
· Test | · 测试(自我测试,修改代码,提交修改) | 90 | 120 |
Reporting | 报告 | 150 | 110 |
· Test Repor | · 测试报告 | 90 | 60 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 40 | 30 |
· 合计 | 1110 | 1470 |
学习进度条
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 0 | 0 | 5 | 5 | 讨论作业要求,并确定要以哪种形式开发以及分工,查找了关于游戏开发的相关资料 |
2 | 300 | 300 | 9 | 14 | 进行了游戏功能的讨论,确定页面跳转的逻辑,共同进行原型设计,学习了微信小程序的基本语法 |
3 | 1100 | 1400 | 18 | 32 | 实现了部分页面的构建;人人对战界面的基本实现 |
4 | 800 | 4200 | 18 | 50 | 实现了页面的动态渲染以及接口的连接,将页面之间的转换按逻辑连接; 完成其他对战模式,全部的游戏逻辑 |
三、心得
- 李晓芳:
- 原型制作是一个比较好玩的过程。一方面,所见即所得的制作方式比较轻松,另一方面,在讨论原型的过程中,我和队友进行了头脑风暴,达成一致的想法都是我们认为好看、有趣、好玩的。
- 每次软工作业都能学到新的东西,虽然学习的质量跟深度有待商榷,但确确实实为我推开很多扇大门。
- 团队之间的沟通十分重要。虽然我和队友经常展开讨论,并且当得出一致结论,但在讨论之后,仍然会对先前讨论的问题有疑惑,这是由于沟通并不十分有效。我们需要探寻更有效的沟通方式。
- 江舒颖:
- 前期虽然查资料会花费很大一部分时间,因此没有编写代码,看起来像是工作毫无进展。但是这个时间一定要花,在完成一个自己没接触过的东西之前,需要去了解它,想清楚到底需要学什么东西,做好准备工作才能更有利于后期的进行。
- 和队友的沟通很重要,关于新事物的理解每个人都不同,所以需要和队友讨论确定一个正确的方向。
- 分工明确很重要,明确的分工才能明确地知道自己要做什么,各司其职,就可以达到1+1>2的效果。