1 初次迭代开发
1.1 基本内容
对于该黄金点游戏,我们进行了首次的沟通、策划后,决定首先实现项目的基础功能,即增量过程模型中的第一个增量(黄金点游戏 Version1.0),其中包括的功能有:
- 采用简单控制台界面的方式实现玩家输入数字的基本功能
- 游戏开始时,可以进行玩家人数和游戏局数的输入设置
- 计算每轮游戏得分,以及统计游戏结束后的总得分
1.2 程序实现流程
1.3 程序实现
import re
import numpy as np
def isSatisfiedNum(x) -> bool:
value = re.compile(r'[0-9]+(\.)?[0-9]*')
isnumber = value.match(x)
if isnumber:
x = float(x)
return 0 <= x <= 100
else:
return False
if __name__ == '__main__':
M, N, G, GOLDEN = 0, 0, 0, 0.618
print("请输入参与游戏的人数:", end='')
N = int(input())
print("请输入游戏的轮数:", end='')
M = int(input())
grades = np.zeros((M, N))
for x in range(M):
print("--------------------------------------------------")
print("第%3d轮:" % (x+1))
print("请玩家依次输入一个0~100的有理数")
i, inputs = 1, []
while (i <= N):
print("玩家%3d:" % i, end='')
number = input()
if not isSatisfiedNum(number):
print("请重新输入0~100的有理数!")
else:
number = float(number)
i += 1
inputs.append(number)
G = sum(inputs) / N * GOLDEN
bias = np.zeros(N)
bias = np.abs(np.array(inputs) - G)
grades[x][bias == np.max(bias)] = -2
grades[x][bias == np.min(bias)] = N
print("本轮各位玩家的得分如下:")
for i in range(N):
print("玩家%3d:" % (i+1), end='')
print("【%3d】分" % grades[x][i])
all_grades = np.sum(grades, axis=0)
print("--------------------------------------------------")
print("各位玩家的最终得分如下:")
for i in range(N):
print("玩家%3d:" % (i+1), end='')
print("【%3d】分" % all_grades[i])
1.4 程序运行结果
2 第二次迭代开发
2.1 增加内容
在第一个版本的基础上,我们决定继续为该游戏增添其余的功能,由此迭代开发形成黄金点游戏 Version2.0。由于该黄金点游戏规则的本身已经得到了实现,下一步应该把视角转移到方便玩家用户的需求上,以及游戏管理者的存储记录需求上。对于这两个总体方面的需求,我们讨论交流之后,首先筛选出了在目前阶段较为重要的需求,其中包括增加以下的功能:
- 游戏玩家的输入UI界面(包括游戏玩家的姓名输入、数字输入,同时数字输入需要隐藏,增加游戏的可玩性)
- 每轮游戏的玩家输入的数字、得分的记录存储
2.2 实现工具与环境配置
- 对于UI界面的设计,我们借助使用Qt Creator工具。在Pycharm集成开发环境中,安装Python3语言相关的库(包括安装第三方模块PyQT5与pyqt5-tools),并设置扩展工具(external tools)的参数,指定QtDesigner、PyUIC(用于把qt的.ui文件转换成.py文件的工具)、PyRcc(用于将资源文件如图片等转成python代码能识别的文件)。
pip install pyqt5
pip install pyqt5-tools
- 对于数据存储,我们采用MySQL数据库工具。在Pycharm集成开发环境中,安装Python3语言库pymysql,并使用Navicat工具提供直观的数据库管理界面。
pip install pymysql
2.3 UI界面设计
如下图为玩家用户输入的UI界面。
2.4 UI界面类与函数
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(677, 481)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.edit_name = QtWidgets.QLineEdit(self.centralwidget)
self.edit_name.setGeometry(QtCore.QRect(460, 210, 191, 41))
self.edit_name.setObjectName("edit_name")
self.title = QtWidgets.QLabel(self.centralwidget)
self.title.setGeometry(QtCore.QRect(20, 20, 631, 121))
font = QtGui.QFont()
font.setFamily("Microsoft Tai Le")
font.setPointSize(60)
font.setBold(True)
font.setWeight(75)
self.title.setFont(font)
self.title.setObjectName("title")
self.label_inputName = QtWidgets.QLabel(self.centralwidget)
self.label_inputName.setGeometry(QtCore.QRect(20, 210, 271, 41))
font = QtGui.QFont()
font.setFamily("黑体")
font.setPointSize(15)
self.label_inputName.setFont(font)
self.label_inputName.setObjectName("label_inputName")
self.label_inputNumber = QtWidgets.QLabel(self.centralwidget)
self.label_inputNumber.setGeometry(QtCore.QRect(20, 280, 431, 41))
font = QtGui.QFont()
font.setFamily("黑体")
font.setPointSize(15)
self.label_inputNumber.setFont(font)
self.label_inputNumber.setObjectName("label_inputNumber")
self.edit_number = QtWidgets.QLineEdit(self.centralwidget)
self.edit_number.setGeometry(QtCore.QRect(460, 280, 191, 41))
self.edit_number.setEchoMode(QtWidgets.QLineEdit.Password)
self.edit_number.setObjectName("edit_number")
self.button_exit = QtWidgets.QPushButton(self.centralwidget)
self.button_exit.setGeometry(QtCore.QRect(20, 360, 131, 51))
font = QtGui.QFont()
font.setFamily("黑体")
font.setPointSize(12)
self.button_exit.setFont(font)
self.button_exit.setObjectName("button_exit")
self.button_ok = QtWidgets.QPushButton(self.centralwidget)
self.button_ok.setGeometry(QtCore.QRect(520, 360, 131, 51))
font = QtGui.QFont()
font.setFamily("黑体")
font.setPointSize(12)
self.button_ok.setFont(font)
self.button_ok.setObjectName("button_ok")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 677, 25))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.button_ok.clicked.connect(MainWindow.clickOkButton)
self.button_exit.clicked.connect(MainWindow.clickExitButton)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.title.setText(_translate("MainWindow", "黄金点游戏"))
self.label_inputName.setText(_translate("MainWindow", "请输入你的名字:"))
self.label_inputNumber.setText(_translate("MainWindow", "请输入数字(0~100):"))
self.button_exit.setText(_translate("MainWindow", "退出"))
self.button_ok.setText(_translate("MainWindow", "确定"))
2.5 数据库设计
如下为数据库的初步设计,游戏开始后会建立两张数据表number与grade,其中表number用于存储每局各个玩家输入的数字,表grade用于存储每局各个玩家所获得的分数。
在上表中,第一行为各玩家输入的姓名,之后每一行则是每一轮游戏该玩家获得的分数。
2.6 数据库实现函数
def InsertData(TableName,dic):
try:
conn=pymysql.connect(host='localhost',user='root',passwd='123456',db='test',port=3306) #链接数据库
cur=conn.cursor()
COLstr='' #列的字段
ROWstr='' #行字段
ColumnStyle=' VARCHAR(20)'
for key in dic.keys():
COLstr=COLstr+' '+key+ColumnStyle+','
ROWstr=(ROWstr+'"%s"'+',')%(dic[key])
#判断表是否存在,存在执行try,不存在执行except新建表,再insert
try:
cur.execute("SELECT * FROM %s"%(TableName))
cur.execute("INSERT INTO %s VALUES (%s)"%(TableName,ROWstr[:-1]))
except pymysql.Error as e:
cur.execute("CREATE TABLE %s (%s)"%(TableName,COLstr[:-1]))
cur.execute("INSERT INTO %s VALUES (%s)"%(TableName,ROWstr[:-1]))
conn.commit()
cur.close()
conn.close()
except pymysql.Error as e:
print ("Mysql Error %d: %s" % (e.args[0], e.args[1]))
2.7 游戏运行程序
我们对于黄金点游戏 Version1.0中的主要程序结构进行了优化,增强其可拓展性,便于更好地迭代开发,方便用于本次的UI界面设计和数据库连接。具体的项目文件见本项目的Github链接:https://github.com/SleepSupreme/Golden-Point-Game,以下为其中的关键程序实现。
class InputWindow(Ui_MainWindow, QMainWindow):
def __init__(self):
super(InputWindow, self).__init__()
self.setupUi(self)
self.GOLDEN = 0.618 # 黄金分割点常数
self.PLAYER, self.ROUND = 3, 2 # 玩家数 & 游戏轮数
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)
for item in self.dir_grade.items():
print("【%s\t%3d分】" % (item[0], item[1]))
print("--------------------第%2d局游戏结果--------------------" % self.cnt_round)
self.cnt_player = 0 # 初始化
self.dir_number, self.dir_grade = {}, {} # 初始化
# 整局游戏结束
if self.cnt_round == self.ROUND:
print("\n========================最终游戏结果========================")
for item in self.dir_all_grade.items():
print("【%s\t%3d分】" % (item[0], item[1]))
print("========================最终游戏结果========================")
self.close()
# 输入数不合法
else:
QMessageBox.warning(self, "警告", "请输入0~100的有理数")
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("Dictionary is empty")
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]
3 总结与下一步计划讨论
本次软件工程实验中,我们进行了两次的迭代开发,对于软件工程开发的基本流程中的沟通、策划、建模(分析、设计)、构建(编码、测试)、部署(交付、反馈)这些阶段,也开始逐渐有了体会与理解。例如,在最开始黄金点游戏设计时,我们需要考虑项目的基本功能需求,即类似于核心产品设计,我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述?怎么细化每一个功能?等等。而在后来的第二次的增量设计,我们更关注于用户与管理者的需求,从而将重点视角进行转移。
在考虑需求之后,关键就是如何设计,这个阶段的主要工作是将产品需求转化为设计需求,从而指导后续的编码工作。设计需求一般阐述了产品需求的详细设计方案,包括页面布局、数据结构、算法以及易用性、安全性、可扩展性、健壮性和性能等诸多方面的设计思路,而本次设计中就包括数据库的设计与UI界面的设计,设计的具体细则、与程序编码的结合实现、用户对象的体验等等都是我们在设计时讨论的内容。
对于下一步计划,我们可以优化游戏UI界面的设计,增强玩家的真实体验感,可以设计更多的游戏设置功能、多个按钮弹窗、游戏结果的展示等等,而数据库的设计中也需要继续优化,如何清晰直观地展示数据?如何确保数据的有效性?如何将记录的数据进行筛选?需要进行对黄金点G的记录和研究吗?这些都是我们在后面的阶段可以讨论与改进的。另一方面,在游戏主程序上,也可以讨论增加其他的规则与功能,如AI算法对于黄金点的预判、Client/Server的设计等等。因此,我们采用迭代开发与结对编程,这是一个互相督促的过程,也是一个长期需要讨论交流的过程,下一次具体的需求设计与编码实现都需要我们进行后续详细的讨论确定。