结对编程作业
https://github.com/juruoQAQ/PigTail
姓名 | 学号 | 分工 | 博客链接 |
---|---|---|---|
余育洲 | 031902329 | 负责AI算法、原型设计、在线AI对战 | https://www.cnblogs.com/juruoQAQ/p/15452333.html |
卢婧 | 031902339 | 负责前端界面、游戏逻辑 | https://www.cnblogs.com/queen-joker/p/15456157.html |
一、原型设计
-
原型设计说明
- 原型设计参考:B站视频——NS版世界游戏大全51猪尾巴游戏
- 原型设计工具:墨刀原型设计工具
- 抠图工具:PS、Clipping Magic
- 图像超分:浩东大佬友情提供的Waifu2x-Extension-GUI
-
此次原型作品共包括模式选择、双人对战、人机对战、登陆界面、游戏大厅、在线对战六个界面,下面给出个界面的具体信息:
-
说明:博客中原型作品贴的都是静态图,但在真实的原型设计演示中,每个界面的初始状态都为全黑,在600ms的定时器事件下逐渐转换到各界面的主页面
-
模式选择:在此页面可进行双人对战、人机对战、在线对战,点击对应按钮可跳转到对应模式。
-
双人对战:
- 双人对战(本地对战)游戏双方在同一台设备上操作,根据确定与取消按钮的位置确定回合。**
- 实时显示卡组、放置区的卡牌数量还有放置区顶部卡牌花色,以及玩家各花色卡牌的数量,
- 提供托管功能,点击小机器人图标即可进入托管模式,再次点击即可退出托管模式,进入与退出皆有弹窗提示
- 游戏结束时,会有自动弹窗提示,根据前端小姐姐的某种设定,1P玩家为小哥,2P为吴邪
(一看就是老盗墓笔记粉了)
-
人机对战:
- 人机对战的页面组成与双人对战相似,唯一的不同点在于确定与取消按钮始终只在2P玩家区域(即吴邪)
- 对人机对战的玩家不再提供托管功能
(总不能让两个AI打吧) - 对局结束的弹窗提示等于双人对战相似,这里就不再贴相同图了
-
登录界面:
- 用户通过输入学号和密码进行登录操作,并跳转至游戏大厅界面。
- 用户通过输入学号和密码进行登录操作,并跳转至游戏大厅界面。
-
游戏大厅:
- 游戏大厅界面提供创建对局、加入对局、以及查询公开对局房间的功能
- 创建对局功能中可选择创建的对局房间的属性为公开或私有
- 查询公开对局房间功能支持翻页查看
- 在游戏大厅中加入对局后即会跳转进入在线对战界面
-
在线对战:
- 在线对战的界面功能与双人对战基本相同、也提供托管功能。这里就不多贴相同的图了。
- 在线对战的界面功能与双人对战基本相同、也提供托管功能。这里就不多贴相同的图了。
-
-
困难及解决方法
- 困难:
- 第一次设计原型、第一次使用墨刀,导致使用过程充满磕磕绊绊,自己设想了许多花里胡哨的功能,但理想很丰满、现实很骨感,很多功能最终都无法实现,只能不断修改,直到弄出相对满意的界面。
- 由于我们选择制作微信小程序,这种竖版的排版让我感觉有点难搞,一方面是空间太小,想要既弄得美观又想对加点功能在上面,布局过程真的很痛苦。另一方面是两个玩家的界面要是相反的,每次都要把图片什么的反过来,经常忘记导致要去不断修改
- 图片素材的收集,每个界面的背景和扑克样式都花了好多时间去找,有时候找到的图片又会有水印或模糊等毛病
- 解决过程
- 只能说度娘yyds,很多功能不懂如何实现都是靠度娘做出来的,还因此发现了很多墨刀的隐藏功能。总之,遇事不决找度娘、度娘不行找Google,Google不行找Wiki
- 至于素材问题,通过去一些素材广场与设计网站,找到了合适的素材,同时,也借鉴了设计网站上一些微信小程序作品在布局上的想法
- 收获
- 学会了墨刀的基础使用与隐藏功能,同时因为找素材在各大设计网站、素材网站流连,看了许多大神的作品。感觉自己的审美能力UPUPUP
- 困难:
二、原型设计实现
- 代码实现思路
- 说明:因为我们是一个人写前端,一个人写AI和AI在线对战部分。所以这部分的内容我们也是分为前端和AI两部分写的。
- 前端部分
- 网络接口的使用
- 这部分使用了微信小程序自带的接入接口 wx.request,使其能与后端交互
wx.request({
url:"http://172.17.173.97:8080/api/user/login",
method:"POST",
data:{
student_id: student_id,
password: password,
},
header: {
"Content-Type" : 'application/x-www-form-urlencoded '
},
success(res){
console.log(res.data.data.token)
if(res.data.message=="Success"){
wx.setStorage({
key:"token",
data:res.data.data.token
})
wx.redirectTo({
url:"/pages/gamelobby/gamelobby"
})
}
else{
wx.showToast({
title: '账号或密码错误,请重试',
icon: 'none',
duration: 1500
})
}
},
fail(res){
console.log(res);
wx.showToast({
title: '网络错误,请重试',
icon: 'none',
duration: 1500
})
}
})
-
代码组织与内部实现设计
-
前端部分设置了若干个数组,用于存放不同花色的扑克牌以及牌堆和出牌区的牌,在选择了相关扑克进行出牌或选牌时,数组的值发生相应变化。对于小程序的页面加载和初次渲染没做过多处理。通过将不同的触发事件分别设置函数,清晰明了地进行相互调用。例如:
-
clickcard:用于点击牌堆之后更新牌堆参数与手牌参数
-
getcurrentplayer:获取当前回合玩家信息
-
clickconfirm:用于确定选牌,更新每个存牌数组的信息,并更新每个区域展示的牌
-
clickcancel:用于点错牌后,还没确认出牌前,可以取消这一次对牌点击后相关参数的改变,玩家可重新选择出牌或选牌
-
tuoguan1:可进行托管,并使用wx.showModal({})提示玩家托管开始。再次点击托管图标后,依旧有弹窗提示,并取消托管。(当然,托管功能还可以继续优化(ノ=Д=)ノ┻━┻)
-
outcard:出牌并更新扑克信息
-
touchcard:选牌并更新相关信息
-
-
前端流程图
- 前端逻辑实现的关键就是要理清每个触发事件以及它们之间的关系,对于所设的函数条理要清晰,相互调用的关系要理顺。一开始打算用web端实现,奈何页面样式写好后逻辑思路陷入死区(QAQ)。后来参考了GitHub上相关的纸牌游戏的小程序代码以及咨询了部分同学,终于(QAQ),还算及时地完成了作业(T ^ T)。但我相信我会继续进步的(ง •̀_•́)ง。
-
前端较为重要的代码片段
js部分:这是在玩家点击了牌堆的牌之后,对相关数组的更新,以及随机产生一张牌,即为玩家选的牌
touchCard:function(){
var player;
player=this.getCurrentPlayer();
var number=this.data.index;
if(this.data.cardPool.length==0){
return;
}
var topCard=this.data.cardPool[number];
this.data.cardPool.splice(number, 1);
this.setData({
cardPool: this.data.cardPool
});
console.log(topCard);
number=Math.floor(Math.random() * this.data.cardPool.length);
this.setData({
index:number,
});
if(this.data.cardShowed.length==0||!this.judgeSuit(topCard,this.data.cardShowed[0])){//若展示区为空或者牌堆顶牌不一致,则牌加入展示区
this.data.cardShowed.unshift(topCard);
this.setData({
cardShowed: this.data.cardShowed
})
}
else{
this.data.cardShowed.unshift(topCard);
this.setData({
cardShowed: this.data.cardShowed
})
this.getCard(player)
}
}
css部分:这是对玩家每种花色的牌进行数量显示,使玩家能随时知道自己手上牌变化的相关样式
.numA{
height: 5px;
width: 200px;
background-color: rgba(0,0,0,0);
display: flex;
margin: 0 15px;
}
.diomondAnum{
width: 52px;
height: 5px;
margin: 0 26px;
color: white;
}
.heartAnum{
width: 52px;
height: 5px;
margin: 0 26px;
color: white;
}
.clubAnum {
width: 52px;
height: 5px;
margin: 0 26px;
color: white;
}
.spadeAnum {
width: 52px;
height: 5px;
margin:0 26px;
color: white;
}
-
AI及在线AI对战部分
-
网络接口的使用
先使用登录接口登录,之后再通过输入选择是创建新的对局还是加入一个对局,并通过接口获取这局比赛的uuid。
因为我不太会使用长连接,所以在监听对局状态方面我选择当己方做出操作后就使用获取上步操作去不断监听对局状态,直到到自己的回合。
到己方回合时,就向AI策略判断函数发送请求,AI策略函数通过对场上实时对局情况进行分析,并给出策略
待到AI策略判断函数给出策略后,就通过执行玩家操作接口进行应答。
待获取上步操作的返回消息错误或者发现卡组里没牌时,对决就结束了。此时再使用获取对局信息接口获取对局信息,获知对局结果 -
代码组织与内部实现设计
在线AI部分的代码包括8个函数,下面对每个函数的功能进行简单说明:
login()函数:使用登录接口进行登录操作
creat_game():使用创建新的对局接口创建对局,并返回对局的uuid
join_game(uuid):使用加入一个对局接口加入对局
get_last(uuid):使用获取上步操作接口获取上部操作,并将返回信息以json格式返回
player_do(x,y,uuidji):使用执行玩家操作接口执行操作,并将返回信息以json格式返回
getresult(uuid):使用获取对局信息接口,获取对局信息并返回
aipan():此为AI策略函数,通过对场上实时对局情况进行分析,给出策略
main:对获取上步操作和执行玩家操作返回的对局信息,对对局中卡组、放置区、以及己方的卡牌情况进行实时更新 -
说明算法的关键与关键实现部分流程图
算法的关键就在于AI怎么实时根据场上对局情况给出策略。下面给出流程图:
-
贴出你认为重要的/有价值的代码片段,并解释
我认为重要的代码就是AI策略选择函数,下面给出代码:
def aipan():
"""
ai策略判断算法
"""
if open['count']!=0:
"""
此时放置区有牌
"""
if ai['count']<=8:
"""
当手牌数小于等于8时
"""
if deck['most']!=open['top']:
"""
若此时放置区顶的牌的花色与卡组中牌数最多的花色
不相同,则选择抽牌
"""
return 0
else:
"""
若此时放置区顶的牌的花色与卡组中牌数最多的花色
相同,则在手牌中另外三种花色中选择手牌最多的那种花色
"""
jilutype=open['top']
jilunum=ai[jilutype]
ai[jilutype]=0
Max=max(ai['S'],ai['H'],ai['C'],ai['D'])
if Max==0:
"""
若手牌只有与放置区顶相同的花色或者没有牌
则选择抽牌
"""
return 0
if ai['S']==Max:
ai[jilutype]=jilunum
return 1
if ai['H']==Max:
ai[jilutype] = jilunum
return 2
if ai['C']==Max:
ai[jilutype] = jilunum
return 3
if ai['D']==Max:
ai[jilutype] = jilunum
return 4
else:
"""
当手牌数大于8时
"""
jilutype1 = open['top']
jilunum1 = ai[jilutype1]
ai[jilutype1]=0
jilutype2 = deck['most']
if ai[jilutype2]!=0 :
"""
若此时手牌中有与卡组中牌最多的花色一致的牌,则打出这个花色
"""
if jilutype2=='S':
ai[jilutype1] = jilunum1
return 1
if jilutype2=='H':
ai[jilutype1] = jilunum1
return 2
if jilutype2=='C':
ai[jilutype1] = jilunum1
return 3
if jilutype2=='D':
ai[jilutype1] = jilunum1
return 4
else:
"""
若此时手牌中没有与卡组中牌最多的花色一致的牌
则打出非这个花色的手牌中数量最多的花色
"""
Max = max(ai['S'], ai['H'], ai['C'], ai['D'])
if Max == 0:
"""
若手牌只有与放置区顶相同的花色
则选择抽牌
"""
return 0
if ai['S'] == Max:
ai[jilutype1] = jilunum1
return 1
if ai['H'] == Max:
ai[jilutype1] = jilunum1
return 2
if ai['C'] == Max:
ai[jilutype1] = jilunum1
return 3
if ai['D'] == Max:
ai[jilutype1] = jilunum1
return 4
else:
"""
放置区没牌
"""
jilutype3=deck['most']
if ai[jilutype3]!=0:
"""
若此时手牌中有与卡组中牌数最多的花色一致
的牌,则打出这张牌
"""
if jilutype3 == 'S':
return 1
if jilutype3 == 'H':
return 2
if jilutype3 == 'C':
return 3
if jilutype3 == 'D':
return 4
else:
"""
否则,就打出自己手中牌数最多的花色
"""
if ai['count']==0:
return 0
else:
Max = max(ai['S'], ai['H'], ai['C'], ai['D'])
if ai['S'] == Max:
return 1
if ai['H'] == Max:
return 2
if ai['C'] == Max:
return 3
if ai['D'] == Max:
return 4
解释:函数通过场上卡牌的分布情况,判断放置区是否有牌、卡组中牌数最多的花色、放置区的顶部的牌的花色、以及己方牌堆中牌的数量、不同花色的牌的比例给出策略。
- 性能分析与改进
性能总体上还尚可,但由于使用的是简单的if else语句,所以策略的准确性会受到影响。可以在这方面进行改进
def player_do(x,y,uuidji):
"""
执行玩家操作
"""
datas["type"] = x
if x==1:
datas["card"] = str(y)
uuid=uuidji
url = "http://172.17.173.97:9000/api/game/" + uuid
r = requests.put(
url=url,
data=json.dumps(datas),
headers=headers
)
return r.json() #将接口返回的信息以json类型返回
-
描述你改进的思路
一方面,可以通过使用深度学习,进行模型的学习或者使用博弈树等方法,进行局部最优处理
另一方面,可以通过分析对方手中的卡牌的情况,判断对方可能的策略,再根据对方的策略进行处理 -
贴出Github的代码签入记录,合理记录commit信息
-
遇到的代码模块异常或结对困难及解决方法
-
余育洲:
- 困难:一方面在于AI算法想了好久,想使用人工智能课上学到的博弈树或者卷积神经网络,但真要写却啥都写不出来。到了最后ddl了,没办法,只能if else简单处理了;另一方面在于接口没使用过,写的时候一堆bug
解决过程:与其他小伙伴讨论,询问大佬和百度。
收获:虽然AI算法没能用上博弈树或者卷积神经网络,但学习了很多这方面的知识,感觉受益匪浅,希望下次我能写出更优秀的AI
- 困难:一方面在于AI算法想了好久,想使用人工智能课上学到的博弈树或者卷积神经网络,但真要写却啥都写不出来。到了最后ddl了,没办法,只能if else简单处理了;另一方面在于接口没使用过,写的时候一堆bug
-
卢婧:
- 困难:前端部分最初没打算用小程序实现,网页的样式都设计好了以后,发现逻辑陷入死区,绕不过来了,扑克的信息没法动态记录好。
解决过程后来,查阅了相关资料,并且询问同学,参考了部分示例代码,终于能按时交作业。
收获在这期间,我对于此游戏的逻辑思路更清晰,对于若干函数间的调用更熟练,并且学习了小程序的相关知识,感觉自己又充实了呢。
- 困难:前端部分最初没打算用小程序实现,网页的样式都设计好了以后,发现逻辑陷入死区,绕不过来了,扑克的信息没法动态记录好。
-
-
评价你的队友。
- 余育洲:
- 由于自己没写过前端,因此小程序的前端和逻辑都是队友写的。很感谢队友的付出
- 需要改进的地方就在于好像不太卷啊哈哈哈哈
- 卢婧:我的队友最值得学习的地方就是能静下心来肝代码(或许只是太能肝了),比较有耐心、遇到困难不抱怨值得表扬❀我觉得对方需要改进的地方就是,明明内心想当卷王(maybe不是),却天天说自己摸鱼。算法写的不错让我自惭形秽。
- 余育洲:
-
5.PSP和学习进度条
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 40 |
· Estimate | · 估计这个任务需要多少时间 | 20 | 40 |
Development | 开发 | 1750 | 3275 |
· Analysis | · 需求分析 (包括学习新技术) | 240 | 360 |
· Design Spec | · 生成设计文档 | 10 | 30 |
· Design Review | · 设计复审 | 5 | 25 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 45 |
· Design | · 具体设计 | 120 | 255 |
· Coding | · 具体编码 | 600 | 1500 |
· Code Review | · 代码复审 | 660 | 1000 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 60 |
Reporting | 报告 | 100 | 120 |
· Test Repor | · 测试报告 | 60 | 80 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
· 合计 | 1870 | 3435 |
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 400 | 400 | 8 | 8 | 复习了前端相关语言、Python,并找好了本次作业需要用到的图片 |
2 | 600 | 1000 | 8 | 16 | 写好了相关页面及样式,构思好了AI实现的基本思路 |
3 | 800 | 1800 | 10 | 26 | 更换前端实现页面,学习小程序相关知识,学习前后端接口与交互 |
4 | 600 | 2400 | 12 | 38 | 完成基本界面并美化,完成AI部分,能够实现前后端交互 |
三、心得
- 卢婧:刚开始看到题目时,我:啊?What?之前学过一些前端知识,有点点放心,但没实践过,还是很担心,不知道从何下手,感觉自己头发都少了很多。后来没思路的时候真的压力很大,觉得自己很差劲,很害怕拖累队友,但是又不得不继续往前走,继续做下去,毕竟逃避解决不了问题。个人认为本次作业前端较难的部分就是实现好游戏的底层逻辑,理清各个触发事件间的联系,并且存储好牌的信息。之前学习过前端相关语言后,一直没有进行实践,刚开始以为此次作业的思路清晰,不会太困难,但没想到还是没能理清相关逻辑,最后请教了同学,才得以解决。此次作业让我丰富了不少知识,学习了小程序的相关内容,我觉得自己又充实了不少,对于一个项目中前度负责的部分也更明确。收获颇丰,我希望自己能有更多实践经历,更加充实,也希望能结交更多能够与我相互讨论问题的同学。总之,好好加油,冲!
- 余育洲:个人觉得这次作业还是有很大难度的,因为此前从未接触过这部分的内容,对前端部分我可谓是一头雾水。还好我的队友很给力,一个人就完成了前端界面和逻辑的编写。
关于我自己的算法部分,我感觉还是差了很多的。虽然之前尝试学了很多深度学习的算法,比如卷积神经网络、KNN。甚至连人工智能课上的博弈树都认真考虑了。但最后理论知识MAX,到了实际编码部分,却啥都写不出来。就算勉强写出一点,也是BUG一堆,最后还是选择了摆烂写了个if else的AI。
不过我比较满意的在于我这么简陋的AI竟然打败了某位大佬用深度学习写出来的AI。总结就是这完全是一个运气游戏,抽牌没抽好就是GG。
虽然这次作业还有很多不足的地方,但还好最后还是做出了点东西。最后还是要感谢队友的付出,希望团队作业的时候我们的配合会越来越好。