结对编程作业
| 031902341邵明杰 | 031902338林珏 | |
|---|---|---|
| 博客链接 | 博客作业 | 博客作业 |
| 具体分工 | 平均分配 | 平均分配 |
| 游戏逻辑(后端) | ✔ | |
| 游戏算法 | ✔ | |
| 原型设计 | ✔ | |
| 游戏界面(前端) | ✔ | |
| 博客编写 | ✔ | ✔ |
| Github | ✔ | ✔ |
| 设计素材 | ✔ | ✔ |
一、原型设计
1、提供此次结对作业的设计说明
原型作品的链接
原型设计说明:账号与密码保持一致即可通过,游戏界面请点击卡组区域(即显示扑克牌背面的那一张图片)即可跳转到下一界面。
-
说明你所采用的原型开发工具:
- Axure Rp、Adobe Photoshop
-
虽然题目叫做猪尾巴,但我们任性的选择了自己设计风格与界面,题名与内容不符,除兔子元素外,所有样式全部为我们自行设计,使用ps完成。原型设计采用登录界面先开始的方式,由登录界面输入账号密码成功后跳转到游戏界面,游戏界面点击托管可显示托管功能,游戏界面点击卡组可跳转到结束界面。最终实际开发中我们将登录界面调整到只有在线模式时需要,并增加了一个创建对局或加入对局的界面,创建时弹出邀请码,加入时要求填写邀请码(此部分为实际开发最终结果,部分内容不在原型链接中展示)。
- 登录界面

- 模式选择界面

- 创建对局或加入对局界面(仅用于在线对战)

- 输入邀请码界面

- 游戏界面

- 托管界面样式

- 游戏结束界面

- 扑克背面

- 扑克样式

- 登录界面
2、遇到的困难及解决方法
最大的一个困难是,响应式开发,由于在最初设计的时候直接把要使用的图片设置为背景,导致背景可以随浏览窗口的大小改变而改变,但所有组件都不能变,最后直接把背景也变为组件,使大家都不能变,但是这就是直接暴力不响应式开发了。另一个是学习周期短,对一些概念模糊不清,没有清楚部件的使用只清楚地知道常用部件的使用。有什么特点,有什么用途,在制作原型的时候可以用在原型的什么地方,在原型中所起的作用是什么,等等,导致我们完成设计的时间大大延长,当然,对于可以靠熟练度来解决的问题应该从自身找原因。
二、原型设计实现
1、代码实现思路:
- 网络接口的使用
选择使用python与pyqt5进行开发,只调用了作业提供的在线对战接口,展示如下。
def login(id, passw):
url_api = 'http://172.17.173.97:8080/api/user/login'
# 登录
id = id
passw = passw
data = { # 这里传递参数
"student_id": id,
"password": passw
}
response1 = requests.post(url=url_api, data=data)
dict1 = response1.json()
if dict1['status'] != 200:
return False
re_data1 = dict1['data']
token = re_data1["token"]
return token
def createGame(pri, token):
# 创建对局
url_api2 = "http://172.17.173.97:9000/api/game"
data2 = {
"private": pri
}
headers = {'Authorization': 'Bearer {}'.format(token)}
response2 = requests.post(url=url_api2, headers=headers, data=data2)
dict2 = response2.json()
re_data2 = dict2['data']
uuid = re_data2["uuid"]
print(dict2)
return uuid
def joinGame(uuid, token):
# 加入对局
url_api3 = "http://172.17.173.97:9000/api/game/{}".format(uuid)
headers = {'Authorization': 'Bearer {}'.format(token)}
response3 = requests.post(url=url_api3, headers=headers)
dict3 = response3.json()
print(dict3)
def previousOption(token, uid):
# 获取上一步操作
headers = {'Authorization': 'Bearer {}'.format(token)}
url_api4 = "http://172.17.173.97:9000/api/game/{}/last".format(uid)
response4 = requests.get(url_api4, headers=headers)
dict4 = response4.json()
print(dict4)
return dict4
-
代码组织与内部实现设计(类图)
-
代码组织架构流程图类图

-
代码组织架构流程图

-
-
说明算法的关键与关键实现部分流程图
算法的关键大概就是AI模块吧,针对对局所进行到的状况分为两种应对方式:
1、如果此时对局已经过半:- 两者手牌相差数量很大
- 少的一方加上放置区所有牌和卡组剩余牌数二倍都不及多的一方多,那就少的一方就直接抽牌到结束,多的一方就出牌或者等结束;
- 排除上一种情况少的人也进行抽牌;
- 多的人则计算剩余牌中概率最大的花色,去企图可以和少的一方相撞
-两者手牌数相差不大; - 如果放置区的牌很多,在己方有手牌有选择权时,出牌计算概率撞花色,对方抽牌我出牌,对方出牌我出牌,(小声说道说道,总觉得这里应该是不太合理的),否则抽牌。
2、此时对局未过半:
- 一般情况下抽牌;
- 参照对局过半时手牌相差很大的状况。

- 两者手牌相差数量很大
-
贴出你认为重要的/有价值的代码片段,并解释
其他的部分代码都是敲键盘那种,写一个玩家类,然后类的函数相互调用,没有使用特殊方法情况下,理清逻辑后就可以写个几百行,只有在策略分析的时候,那一种如何应对的策略才是自己纯纯的脑抽、使用智商余额不足的脑子写出来的,以下展示的就是上一部分所描述的算法关键,详情可参考那里。
def Tactics(self, sum, state): # 调用策略分析,即为AI
if len(already_pok) >= 26:
if self.sum + len(dispostion_pok) + 2*len(reminant_cards) < sum: # 我是少的
params = self.draw_poker()
digit = executeActions(self.token, self.uuid, params) # 滚去抽牌
self.Poker_statistics(digit)
if sum - self.sum >= 13: # 我是少的
params = self.draw_poker()
digit = executeActions(self.token, self.uuid, params) # 抽牌
self.Poker_statistics(digit)
elif sum + len(dispostion_pok) + 2*len(reminant_cards) < self.sum:
params = self.draw_poker()
digit = executeActions(self.token, self.uuid, params) # 抽牌
self.Poker_statistics(digit)
elif self.sum - sum >= 13: # 我是多的
dict = pPRO()
card = ''
for decor in dict:
if decor[0] == 'S' and len(self.poker_s):
card = random.choice(self.poker_s)
self.poker_s.remove(card)
break
if decor[0] == 'H' and len(self.poker_h):
card = random.choice(self.poker_h)
self.poker_h.remove(card)
break
if decor[0] == 'C' and len(self.poker_c):
card = random.choice(self.poker_c)
self.poker_c.remove(card)
break
if decor[0] == 'D' and len(self.poker_d):
card = random.choice(self.poker_d)
self.poker_d.remove(card)
break
dispostion_pok.append(card)
params = self.play_poker(card)
self.cards.remove(card)
self.sum -= 1
executeActions(self.token, self.uuid, params)
elif abs(sum - self.sum) < 13:
if len(dispostion_pok) >= 13:
if state == 0:
if len(self.cards) != 0:
dict = pPRO()
card = ''
for decor in dict:
if decor[0] == 'S' and len(self.poker_s):
card = random.choice(self.poker_s)
self.poker_s.remove(card)
break
if decor[0] == 'H' and len(self.poker_h):
card = random.choice(self.poker_h)
self.poker_h.remove(card)
break
if decor[0] == 'C' and len(self.poker_c):
card = random.choice(self.poker_c)
self.poker_c.remove(card)
break
if decor[0] == 'D' and len(self.poker_d):
card = random.choice(self.poker_d)
self.poker_d.remove(card)
break
dispostion_pok.append(card)
params = self.play_poker(card)
self.sum -= 1
executeActions(self.token, self.uuid, params)
else:
params = self.draw_poker()
digit = executeActions(self.token, self.uuid, params)
self.Poker_statistics(digit)
else:
params = self.draw_poker()
digit = executeActions(self.token, self.uuid, params)
self.Poker_statistics(digit)
else:
if sum - self.sum >= 13: # 我是少的
params = self.draw_poker()
digit = executeActions(self.token, self.uuid, params)
self.Poker_statistics(digit)
elif self.sum - sum >= 13: # 我是多的
dict = pPRO()
card = ''
for decor in dict:
if decor[0] == 'S' and len(self.poker_s):
card = random.choice(self.poker_s)
self.poker_s.remove(card)
break
if decor[0] == 'H' and len(self.poker_h):
card = random.choice(self.poker_h)
self.poker_h.remove(card)
break
if decor[0] == 'C' and len(self.poker_c):
card = random.choice(self.poker_c)
self.poker_c.remove(card)
break
if decor[0] == 'D' and len(self.poker_d):
card = random.choice(self.poker_d)
self.poker_d.remove(card)
break
dispostion_pok.append(card)
params = self.play_poker(card)
self.sum -= 1
executeActions(self.token, self.uuid, params)
else:
params = self.draw_poker()
digit = executeActions(self.token, self.uuid, params)
self.Poker_statistics(digit)
- 性能分析与改进,描述你改进的思路
- 代码整体架构繁琐不够明朗清晰,函数相互调用搞得乌烟瘴气,前端界面鱼龙混杂,导致最后就是填坑式开发,哪里有坑补哪里,后期需要重新调整整体架构。
- 类的使用需要改进,因为使用的方式过于草率,最后关键变量全用全局变量。。。。。
- 展示性能分析图和程序中消耗最大的函数
性能分析图

按照程序消耗排序

在此可以看出消耗最大的函数为und_btn()和goall(),因为主要是这两个函数在控制游戏过程。
def und_btn(self): # 抽牌
if len(reminant_cards):
if self.r == 1:
c = p1.draw_poker()
if len(dispostion_pok) == 0:
self.card.setStyleSheet("QPushButton{border-image: url(:/gamei/" + c + ".png);}")
self.card.setStyleSheet("QPushButton{background:transparent;}")
self.H1_update()
self.D1_update()
self.C1_update()
self.S1_update()
else:
self.card.setStyleSheet("QPushButton{border-image: url(:/gamei/" + c + ".png);}")
self.total = self.total - 1
self.r = 0
if self.flag2:
self.call_tactics_2()
else:
self.r = 0
else:
c = p2.draw_poker()
if len(dispostion_pok) == 0:
self.card.setStyleSheet("QPushButton{border-image: url(:/gamei/" + c + ".png);}")
self.card.setStyleSheet("QPushButton{background:transparent;}")
self.H2_update()
self.D2_update()
self.C2_update()
self.S2_update()
else:
self.card.setStyleSheet("QPushButton{border-image: url(:/gamei/" + c + ".png);}")
self.total = self.total - 1
if self.flag1:
self.call_tactics_1()
else:
self.r = 1
else:
self.close()
self.ol = show_over()
self.ol.show()
def goAll(self):
self.close()
self.all = show_local()
self.all.show()
msgBox = QMessageBox()
msgBox.setWindowTitle(u'提示')
if ran == 1:
msgBox.setText("玩家1先开始")
else:
msgBox.setText("玩家2先开始")
msgBox.exec_()
- 展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路
下列函数主要测试当抽牌后遇到与放置区最上一张扑克牌的花色相同时,能否顺利吃牌,测试数据主要是从52张扑克牌中随机抽取堆叠看能否正确吃牌。
def Judge_dis(self, digit):
card = dispostion_pok[-1]
if len(dispostion_pok) == 0:
return True
if card[0] == digit[0]: # 判定结果:花色相同
return False
else:
return True
def Poker_statistics(self, digit):
# 自己手牌数据
if len(dispostion_pok) != 0:
if self.Judge_dis(digit):
dispostion_pok.append(digit)
else:
dispostion_pok.append(digit)
for card in dispostion_pok:
if card[0] == 'S':
self.poker_s.append(card)
elif card[0] == 'H':
self.poker_h.append(card)
elif card[0] == 'C':
self.poker_c.append(card)
else:
self.poker_d.append(card)
dispostion_pok.clear()
elif len(dispostion_pok) == 0:
dispostion_pok.append(digit)
self.cards = self.poker_s + self.poker_h + self.poker_c + self.poker_d
self.sum = len(self.cards)
def draw_poker(self):
self.state = 0
while True:
digit = random.choice(reminant_cards)
if digit in already_pok:
pass
else:
break
reminant_cards.remove(digit)
already_pok.append(digit)
if digit[0] == 'S':
al_s.append(digit)
elif digit[0] == 'H':
al_h.append(digit)
elif digit[0] == 'C':
al_c.append(digit)
else:
al_d.append(digit)
self.Poker_statistics(digit)
return digit
2、贴出Github的代码签入记录,合理记录commit信息。

3、遇到的代码模块异常或结对困难及解决方法。
在网络接口那里,最初不太会使用,总是提示各种bug,熟悉了后发现还是比较简单的。
pyqt5使用不熟练,在子类传参过程中总是失败,于是将需要传递的参数改为全局变量,也算是debug能力的提高。
4、评价你的队友。
- 邵明杰:一个学习认真负责,态度诚恳的友好伙伴,学习能力也强,能够充分信任她一定会给你交一份完美答卷。UI界面设计的也很美观,可以一起学习的完美搭档,能找到一个和你一起学习一起进步的队友也不容易,棒棒哒的伙伴!改进的地方是和我一样是个ddl是第一生产力的家伙。
- 林珏:一个认真努力的小伙伴,代码能力也很强,学习能力也很强,关于计算机方面的知识懂得也多,后端算法都是她写的,完全相信她一定可以很好的完成。有很强的行动力,和她在一起能够进步,学到一些东西。是一个十分适合一起学习的小伙伴!改进的地方是跟我一样中间两星期划水比较多。
5、psp表格与学习进度表
- PSP表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 10080 | 7200 |
| · Estimate | · 估计这个任务需要多少时间 | 60 | 120 |
| Development | 开发 | 10080 | 7200 |
| · Analysis | · 需求分析 (包括学习新技术) | 540 | 600 |
| · Design Spec | · 生成设计文档 | 50 | 50 |
| · Design Review | · 设计复审 | 30 | 30 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
| · Design | · 具体设计 | 10 | 10 |
| · Coding | · 具体编码 | 10000 | 9200 |
| · Code Review | · 代码复审 | 600 | 120 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 60 | 120 |
| Reporting | 报告 | 60 | 60 |
| · Test Repor | · 测试报告 | 60 | 60 |
| · Size Measurement | · 计算工作量 | 5 | 5 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
| · 合计 | 31675 | 24815 |
- 学习进度表
| 第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
|---|---|---|---|---|---|
| 1 | 0 | 0 | 10 | 10 | 原型设计的学习,ps的熟练运用 |
| 2 | 300 | 300 | 10 | 20 | 熟悉python语言 |
| 3 | 0 | 300 | 5 | 25 | 熟悉pyqt5语言 |
| 4 | 1313 | 1613 | 20 | 45 | 游戏逻辑与界面的交互 |
三、心得
- 邵明杰:从最初的设计到最后的实现,中间有很多次都是已经确定好了方法,然后翻盘重来,很感谢我的小伙伴,刚好选择了一个目标一致的队友,愿意不断陪我重蹈覆辙、东山再起。这个作业、这个游戏的算法是在网上找不到可以参考的,起初了解了完全信息博弈论、不完全信息博弈论,但自己水平有限,套不进去,全程搭建if-else框架三种模式的实现,然后考虑引入线程实现玩家轮流出牌,但是结果就是发现我是一只菜鸡,我没做到。在这里反省,代码整体架构,类的使用就很混乱,因为两个玩家的放置区扑克牌数据存储等等全部使用全局变量,这就导致,某一函数功能需要使用这些变量而不能作为导出模块重复使用,三个模式下很多代码都是重复的,这里还在想办法精简。今后多线程的知识还要去学,这么好的方法要学会使用,整体来说没有学习到太多新知识,学习到了对网络接口的使用,与小伙伴组队合作的成长,与她所写代码的对接,然后就是在创造bug与解决bug的之间反复横跳,我在中间不停地敲键盘。仍没有摆脱DDL是第一生产力的重度拖延症,拖拖拖磨磨蹭蹭到最后一周代码行数飞快增长。
- 林珏:首先,十分感谢我的队友,我们一路相扶完成此次作业。在这次作业完成过程中,学习了pyQt5的布局方式,第一步先在Qt Desinger下设计图形界面。在完成布局之后进行保存,会生成一个ui结尾的文件,这时候将它转换成py文件。之后对接游戏逻辑,将使用界面与操作相分离这里耗费不少时间,全程要复习面向对象编程的特性。在实现过程中pyqt总是莫名其妙运行不了,但又莫名其妙的运行成功,对网络速度的要求也很高,大抵是代码性能的不够吧,希望在测试组那里不会出现bug可以成功编译运行吧。最初的理想很宏大,可以把动画做漂亮一点,界面更加流畅一些,但最后还是只做出了最基本的样式,但学习永无止境,相信后续伴随能力的提升可以把这个界面做的更美观,致力于一流的用户交互体验,创造美妙的视觉观感。从最开始一个小小的按钮到完成一整个小游戏的运行界面,虽然是最原始、粗犷、粗糙的样式构造出的点击效果,但这一路走来满满都是debug的辛酸与血泪,无数次抓狂与暴躁都没有催生出放弃的想法,我们最终完成了这次作业。

浙公网安备 33010602011771号