1 版本回顾
1.1 Version1
黄金点游戏 Version1包括的功能有:
- 采用简单控制台界面的方式实现玩家输入数字的基本功能
- 游戏开始时,可以进行玩家人数和游戏局数的输入设置
- 计算每轮游戏得分,以及统计游戏结束后的总得分
- 对命令行输出进行格式化,使得输出尽量对齐、美观
游戏运行结果如下:
1.2 Version 2.0
在第一个版本的基础上,我们决定继续为该游戏增添其余的功能,由此迭代开发形成黄金点游戏 Version2.0。由于该黄金点游戏规则的本身已经得到了实现,下一步应该把视角转移到方便玩家用户的需求上,以及游戏管理者的存储记录需求上。对于这两个总体方面的需求,我们讨论交流之后,首先筛选出了在目前阶段较为重要的需求,其中包括增加以下的功能:
- 游戏玩家的输入UI界面(包括游戏玩家的姓名输入、数字输入)
- 数字输入时对外不可见,增加游戏的可玩性与公平性
- 建立数据库,存储每轮游戏的玩家输入的数字、得分
游戏运行结果如下:
1.3 Version 2.9
1.2中的Version2.0版本虽然添加了用户输入姓名以及数字的界面,但是程序内部设定游戏轮数与玩家数,终端输出结果,实际上没有完全实现UI界面和终端后台的分离,真实应用中,没有用户愿意和终端后台打交道。
Version2.9在Version2.0的基础上新添功能:
- 将游戏轮数与参与游戏的人数做成UI,接受用户的输入
- UI输出每一轮游戏的结果以及整局游戏的结果,即各位玩家的得分;最后输出赢家
- 每次用户输入后,清空输入姓名与数字的文本框;每次用户输入数字不合理时,仅清空输入数字的文本框,优化用户体验
本版本作为PyQt UI版黄金点的最终版本,其实还有很多继续改进的空间,但是我们开发计划最终产品不以Qt UI的形式呈现,所以将不再更新。其实从Version2.0到2.9,有很多一点点改动优化的地方,但是博客里为了简化语言并且便于表达,所以划分成立两个大的版本。
1.4 Version2.9新增功能代码
import sys
from utils import *
from PyQt5.QtWidgets import *
from window import Ui_MainWindow
from connect_mysql import *
class Window(Ui_MainWindow, QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setupUi(self)
player, _ = QInputDialog.getInt(self, "玩家数", "请输入玩家数目:", 3, 1, 99, 1)
round, _ = QInputDialog.getInt(self, "局数", "请输入游戏局数:", 2, 1, 99, 1)
self.GOLDEN = 0.618 # 黄金分割点常数
self.PLAYER, self.ROUND = player, round # 玩家数 & 游戏轮数
self.ADD, self.MINUS = self.PLAYER, -2 # 赢家加分 & 输家减分
self.cnt_player, self.cnt_round = 0, 0 # 玩家计数器 & 游戏计数器
self.dir_number, self.dir_grade = {}, {} # 数字字典 & 成绩字典
self.dir_all_grade = {}
self.flag = 0
def clickOkButton(self):
_name, _number = self.edit_name.text(), self.edit_number.text()
print("-->用户输入:", "【姓名】:", _name, "【数字】:", _number)
# 输入数合法
if isSatisfiedNum(_number):
self.cnt_player += 1
self.dir_number[_name] = float(_number)
# 一轮游戏结束
if self.cnt_player == self.PLAYER:
self.cnt_round += 1
# 每局数字存入数据库
InsertData('Number', self.dir_number)
self.calGrades()
# 每局分数存入数据库
InsertData('Grade', self.dir_grade)
print("--------------------第%2d局游戏结果--------------------" % self.cnt_round)
result = ""
for item in self.dir_grade.items():
print("【%s\t%3d分】" % (item[0], item[1]))
result += "%s\t%3d分\n" % (item[0], item[1])
print("--------------------第%2d局游戏结果--------------------" % self.cnt_round)
QMessageBox.information(self, "第%d局得分" % self.cnt_round, result, QMessageBox.Yes | QMessageBox.No)
self.cnt_player = 0 # 初始化
self.dir_number, self.dir_grade = {}, {} # 初始化
# 整局游戏结束
if self.cnt_round == self.ROUND:
print("\n========================最终游戏结果========================")
result, MAX, winners = "", 0, set()
for item in self.dir_all_grade.items():
print("【%s\t%3d分】" % (item[0], item[1]))
result += "%s\t%3d分\n" % (item[0], item[1])
if item[1] >= MAX:
_winner, MAX = item[0], item[1]
for item in self.dir_all_grade.items():
if item[1] == MAX:
winners.add(item[0])
print("========================最终游戏结果========================")
QMessageBox.information(self, "得分", result, QMessageBox.Yes | QMessageBox.No)
QMessageBox.information(self, "赢家", "赢家是:"+str(winners), QMessageBox.Yes | QMessageBox.No)
self.close()
self.edit_name.clear()
self.edit_number.clear()
# 输入数不合法
else:
QMessageBox.warning(self, "警告", "请输入0~100的有理数")
self.edit_number.clear()
def clickExitButton(self):
self.close()
def calGrades(self):
_list_name, _list_number = [], []
for item in self.dir_number.items():
_list_name.append(item[0])
_list_number.append(item[1])
GP = sum(_list_number) / self.PLAYER * self.GOLDEN # get golden point
bias = np.abs(np.array(_list_number) - GP)
_list_grade = np.zeros(self.PLAYER)
_list_grade[bias == np.max(bias)] = -2
_list_grade[bias == np.min(bias)] = self.PLAYER
# 每局分数字典
for i in range(self.PLAYER):
self.dir_grade[_list_name[i]] = _list_grade[i]
# 总分累计字典
if self.flag == 0: # dir_all_grade字典为空
print("-->字典为空")
for i in range(self.PLAYER):
self.dir_all_grade[_list_name[i]] = _list_grade[i]
self.flag = 1
else: # dir_all_grade字典非空
print("-->字典非空")
for i in range(self.PLAYER):
self.dir_all_grade[_list_name[i]] += _list_grade[i]
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
游戏对局结果展示窗口用QMessage
实现
QMessageBox.information(self, "第%d局得分" % self.cnt_round, result, QMessageBox.Yes | QMessageBox.No)
QMessageBox.information(self, "得分", result, QMessageBox.Yes | QMessageBox.No)
QMessageBox.information(self, "赢家", "赢家是:"+str(winners), QMessageBox.Yes | QMessageBox.No)
QmessageBox是一种通用的弹出式对话框,用于显示消息,允许用户通过单击不同的标准按钮对消息进行反馈,每个标准按钮有一个预定义的文本,角色和十六进制数。
QMessageBox类提供了许多常用的弹出式对话框,如提示。警告,错误,询问,关于,等会话框,这些不同类型的QMessageBox对话框只是显示的图标不同,其他的功能是一样的。
QMessage.information(QWdiget parent,title,text,buttons,defaultButton)
弹出消息对话框,各参数解释如下:
- parent:指定的父窗口控件
- title:对话框标题
- text:对话框文本
- buttons:多个标准按钮,默认为ok按钮
- defaultButton:默认选中的标准按钮,默认选中第一个标准按钮
清空数据输入框用文本框
对象的clear方法实现
self.edit_name.clear()
self.edit_number.clear()
游戏参数设定采用QInputDialog
实现
player, _ = QInputDialog.getInt(self, "玩家数", "请输入玩家数目:", 3, 1, 99, 1)
round, _ = QInputDialog.getInt(self, "局数", "请输入游戏局数:", 2, 1, 99, 1)
PyQt的QInputDialog
类继承自QDialog,提供了一种简单方面的对话框来获得用户的单个输入信息。QInputDialog
控件是一个标准对话框,有一个文本框和两个按钮(ok和cancel)组成,当用户单击ok或enter键后,在父窗口可以收集通过QInputDialog
控件输入的信息,QInputDialog
控件是QDialog
标准对话框的一部分,在QInpuTDialog
控件中可以输入数字,字符串或列表中的选项,标签用于提示必要的信息它提供了4种数据类型的输入:
- 字符串型(方法
QInputDialog.getText
) - Int类型数据(方法
QInputDialog.getInt
) - double类型数据(方法
QInputDialog.getDouble
) - 下拉列表框的条目(方法
QInputDialog.getItem
)
我们这里采用方法getInt
,设定默认的玩家数和对局数是3,2,并且设定其最小值均为1,最大值均为99;同时,QInputDialog.getInt
窗口提供了一个增加/减少按钮,设定该按钮对于整数的增加/减少步长为1。
2 新版设计:Version 3.0
2.1 设计思路
在新版本Version3中,我们打算使用微信小程序的形式实现黄金点小游戏。因为,我们考虑到了游戏本身不复杂,我们游戏的亮点能在哪?最后,我们团队成员一致认为本软件开发需要做到“快、准、狠”三点:
- 快:游戏能够便捷的启动、结束,不让用户在等待中耗费激情。这就要求程序是B/S结构,即网页端的应用或者微信小程序、支付宝小程序等以WEB浏览器作为客户端最主要的应用软件。这种模式统一了客户端,将系统功能实现的核心部分集中到服务器上。
- 准:准确获取用户的信息并存储游戏相关的数据。我们最开始还打算自己建立数据库,设置用户登录注册的界面,自行管理用户的账户密码等信息。然而,我们团队成员尚处于学习阶段,万一数据库管理不当丢失或者被黑客攻击,这不仅对我们游戏的运行造成影响,更重要的是暴露的用户的隐私。于是,后来我们想到借助腾讯微信大团队开发的稳定安全的平台,用户通过微信账号登录游戏,我们用过微信小程序API接口存取用户的个人信息与游戏信息,整个数据库虽然由我们设计,但是存储保护都是依托微信平台,安全、准确、放心。
- 狠:所谓“狠”,也许有点抽象,我们想的就是要让用户喜欢上我们的游戏,舍不得离开,甚至愿意花费一点点费用在游戏中。但是我们只是一个简简单单甚至很枯燥的黄金点小游戏,怎么才能努力做到这点呢?这让我想到了几年前在微信平台爆火的“打飞机”和“跳一跳”等无比简单的小游戏,这些游戏都是无比简单但是当时却特别火爆,为什么呢?很显然是微信的“好友效应”,即我的好友玩了,他们还邀请我挑战我,我为了比他们有更高的分数或者与他们互动,于是积极地进入了游戏,于是这样形成了一个游戏玩家的良性循环,大家都有越来越高的热情来玩。简单来说,某种意义上,游戏越简单,潜在玩家基数越大;再加上类似微信平台的好友效应,玩家数量就有可能远远超出预期。
2.2 初次需求分析要点
对于该微信小程序的开发,我们根据之前的设计思路,从一些受欢迎程度高的小程序入手,启发思路,大致形成了以下的新的需求。
- 微信小程序用户登录后发起游戏,然后可以设定开始的游戏局数和人数。
- 游戏普通玩家扫描二维码就可以进行登录自己的微信号,然后输入自己的数字进行比赛。
- 在玩家个人界面,可以查看个人曾经输入的数字,相当于一个历史记录的展示,以及每一局黄金点的曲线变动折线图,这样便可以看到本游戏中数学博弈原理的变化趋势。
2.3 程序实现流程
对于上面的简单需求分析,我们接着进行进一步的研究和继续细化,一步一步确定该微信小程序的设计流程,如下图所示。但要明确的是,这只是我们的总体需求方向,不可避免我们在后续的开发迭代过程中会遇到新的变更和新的需求,这些都会影响和改变我们目前的初步设计。
3 设计小结
在两周的设计过程中,实践上,我们主要进行了之前游戏开发版本的迭代开发,对于Qt实现的UI界面进行了改进和优化,但是由于该版本不适合新的用户需求和设计,经过严密的分析和思考之后,我们决定采用微信小程序为主要形式进行继续地迭代开发。
同时,我们也从理论上再次回顾结合了软件工程理论课程上的知识,其中包括需求工程、设计工程、各种UML图的表示与绘制等等,在这里仅展示出我们对于活动图这一概念的着重理解和实践体会。
活动图,用于表示系统中各种活动的次序,它的应用非常广泛,即可用来描述用例的工作流程,也可用来描述类中某个方法的操作行为。常用于表示业务流程,对系统功能建模,强调对象之间的控制流。活动图是由状态图变化而来的,活动图依据对象状态的变化来捕获动作。活动图中一个活动结束后将立即进入下一个活动,状态图中状态的变迁可能需要事件的触发。主要用于系统功能建模。
需求设计过程中,我们知道,类模型体现了系统的静态结构,用例模型则从用户的角度对系统的动态行为进行了宏观建模,并通过交互模型将对象与消息有机地结合在一起。但有些时候,我们还需要更好地表示行为的细节,这就可以借助于活动图和状态图来实现。
而另一方面,普通活动图虽然明确地说明了整个控制流的过程,但是却没有说明每个活动是由谁做的。对应到编程而言,就是没有明确地表示出每个活动是由什么类来负责的;对应到业务建模,就是没有明确地表示出机构中的哪一个部门负责实施什么操作。为了在简单活动图的基础上,有效地表示各个活动由谁负责的信息,还可以通过泳道图来实现。但这些图都各有其优势和发挥作用,下面是我们对于两者的比较理解。
- 活动图VS传统流程图:程序流程图明确地指定了每个活动的先后顺序,而活动图仅描述了活动和必要的工作顺序,这是两者的根本区别。另外,流程图限于顺序进程,而活动图支持并发进程。
- 活动图VS状态图:状态图注重于由事件驱动的系统的变化状态;活动图注重于从活动到活动的控制流。活动图是状态机的一种特殊情况,其中全部或大多数状态是活动状态,并且全部或大多数转换时通过源状态中活动的完成来触发的。活动图适应状态机的全部特征。活动图和状态图在对一个对象的生命周期建模时都是有用的。