结对编程作业
姓名 | 博客链接 | 具体分工 |
---|---|---|
潘伟君 | 链接 | 原型设计;原型界面实现;游戏设置;本地对战;人机对战;写部分博客 |
刘昌隆 | 链接 | 联机对战;接口使用;AI大战;部分原型设计 |
一、原型设计
1.提供此次结对作业的设计说明,要求:(16分)
(1.1)原型基本信息:
项目 | 内容 |
---|---|
原型作品的链接 | 《同花Boom-桌面版》 |
原型开发工具 | 原型设计采用墨刀,主要原因是:可以在网页上直接设计,比较方便;使用简单,上手迅速,快速实现; |
原型名称 | 同花BOOM |
取名理由 | 一旦在放置区发生“同花”现象,放置区的牌会被全部放入“同花”一方的手牌,即一方的手牌发生“BOOM”(暴增) |
原型种类 | 桌面端可执行文件的原型 |
(1.2)原型详细介绍:
(1.2.1)原型思维导图:
(1.2.2)原型设计思路:
可能是受游戏《荒野大镖客》(第二次工业革命背景)《天国拯救》(中世纪背景)中酒馆场景的扑克牌/骰子小游戏的影响,说到这样的卡牌小游戏我脑海中第一个场景就是酒馆的一张简单木桌上的双方博弈。因此原型的背景就是“木板”主题背景,此外选择灰底白字带阴影的按钮,深色半透明的区域框,来设计基本界面
(1.2.3)原型展示:
登入界面:
主 页面:
开始游戏-模式选择 页面:
本地对战 页面:
人机对战 页面:
联机对战选择:
联机对战 页面:
游戏规则 页面:
游戏设置 页面:
2.遇到的困难及解决方法:(4分)
(2.1)困难描述(1分):
① 之前没有接触、使用过原型设计工具,因此在原型设计方面比较陌生;
② 原型设计没有想要的素材;
(2.2)解决过程(2分):
① 参考同学的意见,选择比较容易上手的“墨刀”,通过学习“墨刀”给出的原型范例,结合自己的想法,逐渐设计出了原型;
② 使用“墨刀”提供的元素库,再自己进行一些的PS工作来获得想要的素材;
(2.3)收获(1分):
① 了解到了原型设计,因为我(潘)也负责一部分代码编写,因此对原型设计的必要性有了更深刻的理解,原型设计在一定程度上指导了代码的编写;
② 锻炼了自己寻找收集、创造素材的能力,锻炼了PS能力;
二、原型设计实现
1.代码实现思路:(11分)
(1.1)代码实现思路:(11分)
(1.1.1)网络接口的使用
利用python的request模块请求接口,这里以登入获取token为例子。在多次连接后如果网络没有连接上就会报错。
def get_token():
print("账号:%s" % v1.get())
print("密码:%s" % v2.get())
headers["Content-Type"] = "application/json;charset=UTF-8"
r = {}
datas['student_id'] = v1.get()
datas['password'] = v2.get()
"""获取token"""
fail = 0
url = "http://172.17.173.97:8080/api/user/login"
r = requests.post(url=url, data=datas)
if r.status_code == 200:
r = r.json()
mes = r['message']
if mes == "Success":
headers["Authorization"] =r["data"]["token"]
root.destroy()
page_start_menu()
else:
print('输入信息错误')
else:
print("网络连接出现问题")
print(r)
(1.1.2)代码组织与内部实现设计(类图)
(1.1.3)说明算法的关键与关键实现部分流程图
算法的关键就是:游戏逻辑、AI逻辑,游戏逻辑根据游戏规则得到,AI逻辑模仿决策过程,流程图如下:
① 游戏逻辑:
② AI逻辑:
③ 联机逻辑:
(1.1.4)贴出你认为重要的/有价值的代码片段,并解释(2分)
① 游戏进程的主循环,解释:游戏的动脉,循环停了,游戏也就GO DIE了
while len(deck) != 0:
for event in pygame.event.get():
if len(deck) == 0:
break
if event.type == pygame.MOUSEMOTION:
continue # 是鼠标移动事件则跳到下一个事件
if event.type == pygame.QUIT: # 点击关闭窗口
sys.exit()
elif event.type == pygame.MOUSEBUTTONUP: # 点击事件
if event.button == 1: # 左键点击
if event.pos[0] in
……省略……
② AI的实现,解释:模拟人的决策过程,输入对局情况now_info,输出对策0~4
# now_info: 当前场上局势,
# 0-3: my_card 自己手牌
# 4-7: opponent_card 对手手牌
# 8-11: deck_card 牌堆牌
# 12-15: placement_card 放置区牌
# 16 top_type_of_placement_area 放置区牌顶
# answer:
# 0-出手牌黑桃
# 1-出手牌红心
# 2-出手牌梅花
# 3-出手牌方块
# 4-牌堆出排
def p_ai(now_info):
# 设定应该保留的手牌数
amount_to_keep = 4
my_card = [now_info[0], now_info[1], now_info[2], now_info[3]]
opponent_card = [now_info[4], now_info[5], now_info[6], now_info[7]]
deck_card = [now_info[8], now_info[9], now_info[10], now_info[11]]
placement_card = [now_info[12], now_info[13], now_info[14], now_info[15]]
top_type_of_placement_area = now_info[16]
my_card_amount = 0
opponent_card_amount = 0
deck_card_amount = 0
placement_card_amount = 0
for type_i in range(0, 4):
my_card_amount += my_card[type_i]
opponent_card_amount += opponent_card[type_i]
deck_card_amount += deck_card[type_i]
placement_card_amount += placement_card[type_i]
def get_second_most_cards_type_in_deck(temp_card):
temp_card[temp_card.index(max(temp_card))] = -1
return temp_card.index(max(temp_card))
# 牌堆中最多的牌(牌堆最有可能被抽到的花色)
most_cards_type_in_deck = deck_card.index(max(deck_card))
# 牌堆中第二多的牌
second_most_cards_type_in_deck = get_second_most_cards_type_in_deck(deck_card)
# 把手牌中跟放置区顶相同花色的花色置为0
if top_type_of_placement_area != 4:
my_card[top_type_of_placement_area] = 0
# 己方牌为0时
if my_card_amount == 0:
# 直接抽牌堆
return 4
# 对方没牌而且放置区牌数大于5
if opponent_card_amount == 0 and placement_card_amount >=5:
# 放置区顶不是牌堆最可能出现的牌
if top_type_of_placement_area != most_cards_type_in_deck:
return most_cards_type_in_deck
# 己方牌数 > 对手牌数 时,出手牌
if my_card_amount > opponent_card_amount:
# 最多的牌和放置区冲突 且 除了最多的牌没牌,则抽牌堆
if my_card[my_card.index(max(my_card))] == 0:
return 4
# 否则出最多的牌
else:
return my_card.index(max(my_card))
# 己方牌数 <= 对手牌数 时,安全则抽牌堆,否则出手牌
else:
# 如果牌堆中最可能出现的牌不是放置区顶的牌,则出牌
if most_cards_type_in_deck != top_type_of_placement_area:
return 4
# 否则,在没牌的情况下抽牌,有牌则出牌
else:
if my_card_amount == 0:
return 4
else:
return my_card.index(max(my_card))
③联机实现,解释:用socket连接联机对战的双方,根据收到的消息判断下一步是要获取上步操作生成pygame事件,还是对手已经退出游戏
if turn == 1 and host_client == 0 or turn == 0 and host_client == 1:
if host_client == 0:
recvmsg = client_socket.recv(1024)
strData = recvmsg.decode("gbk")
print(strData)
if strData == "finish":
client_step = get_last()
else:
break
if host_client == 1:
msg = s.recv(1024)
print(msg.decode("gbk"))
if msg.decode("gbk") == "finish":
client_step = get_last()
else:
break
if client_step["data"]["last_code"][2] == '0':
# pyautogui.moveTo(377, 337, duration=0.25)
k = pygame.event.Event(pygame.MOUSEBUTTONUP, {'pos': (337, 337), 'button': 1, 'window': None})
elif client_step["data"]["last_code"][2] == '1' and client_step["data"]["last_code"][4] == 'S':
# pyautogui.moveTo(294, 139, duration=0.25)
k = pygame.event.Event(pygame.MOUSEBUTTONUP, {'pos': (294, 139), 'button': 1, 'window': None})
elif client_step["data"]["last_code"][2] == '1' and client_step["data"]["last_code"][4] == 'H':
# pyautogui.moveTo(456, 139, duration=0.25)
k = pygame.event.Event(pygame.MOUSEBUTTONUP, {'pos': (456, 139), 'button': 1, 'window': None})
elif client_step["data"]["last_code"][2] == '1' and client_step["data"]["last_code"][4] == 'C':
# pyautogui.moveTo(619, 139, duration=0.25)
k = pygame.event.Event(pygame.MOUSEBUTTONUP, {'pos': (619, 139), 'button': 1, 'window': None})
elif client_step["data"]["last_code"][2] == '1' and client_step["data"]["last_code"][4] == 'D':
# pyautogui.moveTo(781, 139, duration=0.25)
k = pygame.event.Event(pygame.MOUSEBUTTONUP, {'pos': (781, 139), 'button': 1, 'window': None})
pygame.event.post(k)
(1.1.5)性能分析与改进
因为代码嵌套的逻辑,在最底层的函数使用时间最长的就是show_sittution(),因为每次执行完成后页面状态的改变都要用show_sittution()来刷新页面。
(1.1.6)描述你改进的思路(2分)
思路是每次刷新只把游戏状态有改变的界面元素进行改变。这样就可以减少重新把所有的元素插入,改变的时间。
(1.1.7)展示性能分析图和程序中消耗最大的函数
函数:
def show_situation(situation_type): # 展示当前场上的情况 screen.blit(background, (0, 0)) # 插入背景 screen.blit(button_return, (872, -7)) # 插入返回按钮 screen.blit(tag_gamer_A, (110, 502)) # 玩家A标牌 screen.blit(tag_AI, (110, 10)) # 玩家B标牌 if turn == 0: screen.blit(tag_A_turn, (467, 248)) # A的回合 else: screen.blit(tag_AI_turn, (467, 248)) # B的回合 screen.blit(tag_cards_in_hand, (215, 502)) # A的手牌区 screen.blit(tag_cards_in_hand, (215, 11)) # B的手牌区 screen.blit(tag_deck, (303, 266)) # 牌堆区 screen.blit(tag_deck_sign, (338, 468)) # 牌堆区标志 screen.blit(tag_deck_amount, (337, 231)) # 牌堆区数量 screen.blit(tag_placement_area, (628, 266)) # 放置区 screen.blit(tag_placement_area_amount, (662, 229)) # 放置区数量 screen.blit(tag_placement_area_sign, (663, 468)) # 放置区标志 screen.blit(tag_S_amount, (252, 512)) # A-黑桃数量标牌 screen.blit(tag_H_amount, (414, 512)) # A-红心数量标牌 screen.blit(tag_C_amount, (577, 512)) # A-梅花数量标牌 screen.blit(tag_D_amount, (739, 512)) # A-方块数量标牌 screen.blit(tag_S_amount, (252, 21)) # B-黑桃数量标牌 screen.blit(tag_H_amount, (414, 21)) # B-红心数量标牌 screen.blit(tag_C_amount, (577, 21)) # B-梅花数量标牌 screen.blit(tag_D_amount, (739, 21)) # B-方块数量标牌 # 展示牌堆 if len(deck) > 0: screen.blit(card_back, (327, 287)) # 牌堆顶是卡牌背面(即不展示) # 展示放置区 if not placement_area.is_empty(): screen.blit(placement_area.peek().card_image, (651, 289)) # 展示手牌 for iii in range(0, 8): if not cards_in_hand[iii].is_empty(): if iii % 4 == 0: if iii < 4: screen.blit(cards_in_hand[iii].peek().card_image, (244, 555)) else: screen.blit(cards_in_hand[iii].peek().card_image, (244, 64)) elif iii % 4 == 1: if iii < 4: screen.blit(cards_in_hand[iii].peek().card_image, (406, 555)) else: screen.blit(cards_in_hand[iii].peek().card_image, (406, 64)) elif iii % 4 == 2: if iii < 4: screen.blit(cards_in_hand[iii].peek().card_image, (569, 555)) else: screen.blit(cards_in_hand[iii].peek().card_image, (569, 64)) elif iii % 4 == 3: if iii < 4: screen.blit(cards_in_hand[iii].peek().card_image, (731, 555)) else: screen.blit(cards_in_hand[iii].peek().card_image, (731, 64)) # 展示当前状况(1:等待出牌,2:安全出排,3:同花BOOM,4:牌堆出牌,5:手牌出牌) if situation_type == 1: screen.blit(tag_wait_play, (467, 327)) # 等待出牌 elif situation_type == 2: screen.blit(tag_safe_play, (467, 327)) # 安全出排 elif situation_type == 3: screen.blit(tag_flush_boom, (467, 327)) # 同花BOOM elif situation_type == 4: # screen.blit(tag_from_deck, (469, 290)) # 牌堆出牌 screen.blit(tag_wait_play, (467, 327)) # 等待出牌 elif situation_type == 5: # screen.blit(tag_from_hand, (469, 290)) # 手牌出牌 screen.blit(tag_wait_play, (467, 327)) # 等待出牌 # 显示牌数 font = pygame.font.SysFont('microsoft Yahei', 30) # 显示牌堆牌数 num = len(deck) card_num = font.render(str(num), False, (255, 255, 255)) screen.blit(card_num, (367, 237)) # 显示放置区牌数 num = placement_area.size() card_num = font.render(str(num), False, (255, 255, 255)) screen.blit(card_num, (692, 237)) # 显示当前玩家牌数 for card_num_i in range(0, 4): num = cards_in_hand[card_num_i].size() card_num = font.render(str(num), False, (255, 255, 255)) if card_num_i == 0: screen.blit(card_num, (312, 524)) elif card_num_i == 1: screen.blit(card_num, (474, 524)) elif card_num_i == 2: screen.blit(card_num, (637, 524)) elif card_num_i == 3: screen.blit(card_num, (799, 524)) # 显示对方玩家牌数 for card_num_i in range(4, 8): num = cards_in_hand[card_num_i].size() card_num = font.render(str(num), False, (255, 255, 255)) if card_num_i == 4: screen.blit(card_num, (312, 33)) elif card_num_i == 5: screen.blit(card_num, (474, 33)) elif card_num_i == 6: screen.blit(card_num, (637, 33)) elif card_num_i == 7: screen.blit(card_num, (799, 33)) # 显示记牌器 if use_card_recorder: show_card_recorder() # 刷新屏幕 pygame.display.flip()
(1.1.8)展示出项目部分单元测试代码,并说明测试的函数,构造测试数据的思路 (2分)
以自己的账号密码对接口单元进行测试,由于隐私把账号和接口url删除
import unittest from main import creat_game import requests datas = {} headers = {'student_id':"", 'password':""} def get_token(): headers["Content-Type"] = "application/json;charset=UTF-8" """获取token""" fail = 0 url = "" r = requests.post(url=url, data=datas) if r.status_code == 200: r = r.json() mes = r['message'] if mes == "Success": headers["Authorization"] = r["data"]["token"] else: print('输入信息错误') else: print("网络连接出现问题") print(r) class MyTestCase(unittest.TestCase): def test_something(self): self.assertEqual(creat_game()['msg'], "操作成功") if __name__ == '__main__': unittest.main()
(1.1.9)贴出Github的代码签入记录,合理记录commit信息。(1分) (2分)
3.遇到的代码模块异常或结对困难及解决方法。(4分)
潘伟君部分:
①
代码模块异常:对局中疯狂点击手牌区/牌堆后,程序崩溃,出现访问非自然数下标的异常
解决方法:把对下标的跟踪和限制改得更加严格,在每次变动下标后都及时判断是否为合法的自然数
②
代码模块异常:在程序运行途中在pycharm中点击“重新运行” 或 在程序运行途中在pycharm中点击“停止”(红色方块),出现keyboardinterrupt异常
解决方法:在网络上查找解决方法无果,不少人也有这个异常但通常是ctrl+c造成的而不是pycharm的“重新运行”、“停止”按钮造成的,查来查去还是一头雾水。因为在可执行文件给出的窗口中并不会出现该异常,即使用状态下没有出现异常,也不影响使用,因此认为是非恶性BUG,暂时放置。
刘昌隆部分:
困难描述(1分):
① 开始学习网络接口使用的时候搞不清楚什么时候请求用json和表单,接口访问一直错误;
② 在写联机部分用while语句来循环访问接口获取数据导致程序执行的时候很卡,想要继续操作却没有反应,不得不关掉程序;
解决过程(2分):
① 在舍友的帮助下,下载一个postman的软件先在里面设置数据来访问接口,来了解接口访问方式,然后再在写就可以轻松写出来,都是格式化的代码,所以了解了方法后编写起来就容易了很多;
② 首先想法是如果可以让对方等待另一方的通知再去访问接口,这样是不是就可以解决了,结果是可以解决一部分问题(比如说程序运行的很卡等),用socket网络连接,接等待接收的时候进程就进入阻塞状态,还是会给人一种不好的体验,只有在对方发送了完成操作的消息才可以操作,程序进入假死状态。进一步的想法是在主程序执行过程中在开一个线程来接收消息和发送消息,但是后来在想要实现的时候发现,我需要在主进程中发送消息和接受消息,所以这个想法只能暂罢,希望后面更多学习的时候可以解决这类网络联机的问题。
收获(1分):
① 在学习的过程中学习原型设计工具工具的使用初始阶段(我只参与其中一小部分设计)感觉自己在设计这方面一点天赋都没有,更加清楚自己未来的定位要往偏向后端技术方向发展。但是有机会还是希望多学一点知识,毕竟技多不压身。
② 在解决联机对战的过程中遇到了非常多的问题,还好学习过网络通信,在了解基础原理上编写代码只要学会用函数就好。
4.评价你的队友。(2分)
潘伟君部分:
值得学习的地方:认真负责,逻辑清晰,经常提出建设性意见
需要改进的地方:可能是因为联机方面难度比较大,联机模块花的时间比较多
刘昌隆部分:
值得学习的地方:认真负责,工作完成效率高。
需要改进的地方:要吐槽的地方好像也没有什么地方,可能在这次配合上第一次没有很默契吧。
5.提供此次结对作业的PSP和学习进度条(每周追加),示例如下(2分)
潘伟君部分:
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 754 | 754 | 20 | 20 | ① 学习实践了原型设计;② 通过够造游戏框架,实现本地对战功能,强化了Python编程能力;③ 熟悉了Pygame的使用:如图片操作,声音操作,跳转;④ 学习了游戏逻辑的实现,程序页面的刷新等等 |
2 | 68 | 822 | 6 | 26 | ① 学习了游戏设置方法:记牌器开关,音量调节,对pygame的使用进一步掌握 |
3 | 522 | 1344 | 12 | 38 | ① 学习了基础AI设计;② 学习了博弈论、贪心算法 |
4 | 0 | 1344 | 5 | 43 | 这周主要写博客和做思维导图、UML图、总结,熟悉了各种图的做法 |
刘昌隆部分:
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 100+ | 100+ | 5 | 5 | 第一周主要是先学习了如何去是使用接口,学习了网络接口的使用方法;设计联机实现方式,花大量的时间在网络上搜索一些其他人设计小游戏的联机方式,学习到用socket去达到客户端和服务器的连接 |
2 | 600+ | 700+ | 20 | 25 | 具体设计编码,这段时间主要在debug,没有什么新的知识摄入 |
3 | 100+ | 800+ | 12 | 37 | 学习了原型设计工具的使用,因为队友是团队编程作业的队长还有很多工作要做 |
4 | 200+ | 1000+ | 10 | 47 | 学习了AI设计的思想,但是最后已经没有时间来实现我的AI算法了,把前面一些遗漏的功能完善了 |
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
· Estimate | · 估计这个任务需要多少时间 | 60 | 50 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 600 | 1100 |
· Design Spec | · 生成设计文档 | 150 | 300 |
· Design Review | · 设计复审 | 50 | 100 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 15 |
· Design | · 具体设计 | 100 | 120 |
· Coding | · 具体编码 | 1600 | 2800 |
· Code Review | · 代码复审 | 150 | 300 |
· Test | · 测试(自我测试,修改代码,提交修改) | 200 | 300 |
Reporting | 报告 | ||
· Test Repor | · 测试报告 | 80 | 150 |
· Size Measurement | · 计算工作量 | 10 | 15 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 45 |
· 合计 | 3090 | 5295 |
三、心得
潘伟君部分:
学习心得:
初识pygame,在实践中掌握了pygame的用法,实现了“同花BOOM”的卡牌小游戏,收益颇丰。
① 掌握了pygame关于图像、声音、鼠标事件等等的处理以及游戏逻辑的实现;
② 学习了AI设计,了解了博弈论、贪心;
③ 最重要的是学习到了“学习的方法”和锻炼了信息收集能力、问题解决能力,在面对不懂的技术的时候能够尽快上手进入应用;
作业难度:
作业难度主要集中在联机对战、人机对战上,因为联机对战是队友做的,所以我对人机对战的难度更有体会一些。首先是关于怎么才算“最优走步”的问题,我在实现基本对战之后就和舍友打了一晚上牌,只总结出来几个规律,并不能算得上“最优走步”。继续投入时间下去或许能建模或者尝试下深度学习,但是因为还有其它模块、其它作业没有完成,很可惜地放置了AI的深入。
作业感想:
这次实践作业花的时间不少,但是也学到了挺多东西,比如pygame和原型设计。就是AI有点可惜,因为还有其他课程的作业,没办法再花时间在AI上进一步钻研,只做出来一个just so so的东西。
刘昌隆部分:
学习心得:
①学习了网络接口的使用方法和在python语言上使用TCP连接的方法
②学习了原型设计工具的基础使用
③进一步提高了自己检索资料解决问题的能力,更够更具具体问题分析出自己需要什么再去网络上查询,与一开始盲目先胡乱一通有进步
作业难度:
作业难度主要集中在联机对战、人机对战上,我负责联机部分的实现,在这方面确实每天晚上都因为VPN的问题也拖延了我一部分的进度。如何让玩家有更好的游戏体验是我所需要想的:(1)使用者的角度应该是在界面的下面。(2)用户可以使用托管功能。很可惜的是当选择了托管后进程进入循环,不产生点击事件,不能取消托管。
作业感想:
在以前刚刚开始学python的时候就有用过pygame做个一个飞机打怪兽的非常简单的小游戏,现在在此又用到pygame来实现这次的结对作业。不知道是我的实力有问题还是pygame的功能还是非常有限的,毕竟只是一个python的模块,这次作业完成的还是有些没有达到预期效果,看到舍友大佬和其他人完成的花里胡哨的功能实名羡慕了,希望自己继续学习,而且还是不能局限自己学习的范围,因为不同的语言在完成不同项目功能的时候都有不同的优势前面有说自己更加明确了自己的未来的方向,还是希望多学一点,开拓视野。