1 版本回顧
1.1 Version1
黃金點(diǎn)游戲 Version1包括的功能有:
- 采用簡單控制臺界面的方式實(shí)現(xiàn)玩家輸入數(shù)字的基本功能
- 游戲開始時,可以進(jìn)行玩家人數(shù)和游戲局?jǐn)?shù)的輸入設(shè)置
- 計(jì)算每輪游戲得分,以及統(tǒng)計(jì)游戲結(jié)束后的總得分
- 對命令行輸出進(jìn)行格式化,使得輸出盡量對齊、美觀
游戲運(yùn)行結(jié)果如下:

1.2 Version 2.0
在第一個版本的基礎(chǔ)上,我們決定繼續(xù)為該游戲增添其余的功能,由此迭代開發(fā)形成黃金點(diǎn)游戲 Version2.0。由于該黃金點(diǎn)游戲規(guī)則的本身已經(jīng)得到了實(shí)現(xiàn),下一步應(yīng)該把視角轉(zhuǎn)移到方便玩家用戶的需求上,以及游戲管理者的存儲記錄需求上。對于這兩個總體方面的需求,我們討論交流之后,首先篩選出了在目前階段較為重要的需求,其中包括增加以下的功能:
- 游戲玩家的輸入U(xiǎn)I界面(包括游戲玩家的姓名輸入、數(shù)字輸入)
- 數(shù)字輸入時對外不可見,增加游戲的可玩性與公平性
- 建立數(shù)據(jù)庫,存儲每輪游戲的玩家輸入的數(shù)字、得分
游戲運(yùn)行結(jié)果如下:



1.3 Version 2.9
1.2中的Version2.0版本雖然添加了用戶輸入姓名以及數(shù)字的界面,但是程序內(nèi)部設(shè)定游戲輪數(shù)與玩家數(shù),終端輸出結(jié)果,實(shí)際上沒有完全實(shí)現(xiàn)UI界面和終端后臺的分離,真實(shí)應(yīng)用中,沒有用戶愿意和終端后臺打交道。
Version2.9在Version2.0的基礎(chǔ)上新添功能:
- 將游戲輪數(shù)與參與游戲的人數(shù)做成UI,接受用戶的輸入
- UI輸出每一輪游戲的結(jié)果以及整局游戲的結(jié)果,即各位玩家的得分;最后輸出贏家
- 每次用戶輸入后,清空輸入姓名與數(shù)字的文本框;每次用戶輸入數(shù)字不合理時,僅清空輸入數(shù)字的文本框,優(yōu)化用戶體驗(yàn)





本版本作為PyQt UI版黃金點(diǎn)的最終版本,其實(shí)還有很多繼續(xù)改進(jìn)的空間,但是我們開發(fā)計(jì)劃最終產(chǎn)品不以Qt UI的形式呈現(xiàn),所以將不再更新。其實(shí)從Version2.0到2.9,有很多一點(diǎn)點(diǎn)改動優(yōu)化的地方,但是博客里為了簡化語言并且便于表達(dá),所以劃分成立兩個大的版本。
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, "玩家數(shù)", "請輸入玩家數(shù)目:", 3, 1, 99, 1)
round, _ = QInputDialog.getInt(self, "局?jǐn)?shù)", "請輸入游戲局?jǐn)?shù):", 2, 1, 99, 1)
self.GOLDEN = 0.618 # 黃金分割點(diǎn)常數(shù)
self.PLAYER, self.ROUND = player, round # 玩家數(shù) & 游戲輪數(shù)
self.ADD, self.MINUS = self.PLAYER, -2 # 贏家加分 & 輸家減分
self.cnt_player, self.cnt_round = 0, 0 # 玩家計(jì)數(shù)器 & 游戲計(jì)數(shù)器
self.dir_number, self.dir_grade = {}, {} # 數(shù)字字典 & 成績字典
self.dir_all_grade = {}
self.flag = 0
def clickOkButton(self):
_name, _number = self.edit_name.text(), self.edit_number.text()
print("-->用戶輸入:", "【姓名】:", _name, "【數(shù)字】:", _number)
# 輸入數(shù)合法
if isSatisfiedNum(_number):
self.cnt_player += 1
self.dir_number[_name] = float(_number)
# 一輪游戲結(jié)束
if self.cnt_player == self.PLAYER:
self.cnt_round += 1
# 每局?jǐn)?shù)字存入數(shù)據(jù)庫
InsertData('Number', self.dir_number)
self.calGrades()
# 每局分?jǐn)?shù)存入數(shù)據(jù)庫
InsertData('Grade', self.dir_grade)
print("--------------------第%2d局游戲結(jié)果--------------------" % 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局游戲結(jié)果--------------------" % 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 = {}, {} # 初始化
# 整局游戲結(jié)束
if self.cnt_round == self.ROUND:
print("\n========================最終游戲結(jié)果========================")
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("========================最終游戲結(jié)果========================")
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()
# 輸入數(shù)不合法
else:
QMessageBox.warning(self, "警告", "請輸入0~100的有理數(shù)")
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
# 每局分?jǐn)?shù)字典
for i in range(self.PLAYER):
self.dir_grade[_list_name[i]] = _list_grade[i]
# 總分累計(jì)字典
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_())
游戲?qū)纸Y(jié)果展示窗口用QMessage實(shí)現(xiàn)
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是一種通用的彈出式對話框,用于顯示消息,允許用戶通過單擊不同的標(biāo)準(zhǔn)按鈕對消息進(jìn)行反饋,每個標(biāo)準(zhǔn)按鈕有一個預(yù)定義的文本,角色和十六進(jìn)制數(shù)。
QMessageBox類提供了許多常用的彈出式對話框,如提示。警告,錯誤,詢問,關(guān)于,等會話框,這些不同類型的QMessageBox對話框只是顯示的圖標(biāo)不同,其他的功能是一樣的。
QMessage.information(QWdiget parent,title,text,buttons,defaultButton) 彈出消息對話框,各參數(shù)解釋如下:
- parent:指定的父窗口控件
- title:對話框標(biāo)題
- text:對話框文本
- buttons:多個標(biāo)準(zhǔn)按鈕,默認(rèn)為ok按鈕
- defaultButton:默認(rèn)選中的標(biāo)準(zhǔn)按鈕,默認(rèn)選中第一個標(biāo)準(zhǔn)按鈕
清空數(shù)據(jù)輸入框用文本框對象的clear方法實(shí)現(xiàn)
self.edit_name.clear()
self.edit_number.clear()
游戲參數(shù)設(shè)定采用QInputDialog實(shí)現(xiàn)
player, _ = QInputDialog.getInt(self, "玩家數(shù)", "請輸入玩家數(shù)目:", 3, 1, 99, 1)
round, _ = QInputDialog.getInt(self, "局?jǐn)?shù)", "請輸入游戲局?jǐn)?shù):", 2, 1, 99, 1)
PyQt的QInputDialog類繼承自QDialog,提供了一種簡單方面的對話框來獲得用戶的單個輸入信息。QInputDialog控件是一個標(biāo)準(zhǔn)對話框,有一個文本框和兩個按鈕(ok和cancel)組成,當(dāng)用戶單擊ok或enter鍵后,在父窗口可以收集通過QInputDialog控件輸入的信息,QInputDialog控件是QDialog標(biāo)準(zhǔn)對話框的一部分,在QInpuTDialog控件中可以輸入數(shù)字,字符串或列表中的選項(xiàng),標(biāo)簽用于提示必要的信息它提供了4種數(shù)據(jù)類型的輸入:
- 字符串型(方法
QInputDialog.getText) - Int類型數(shù)據(jù)(方法
QInputDialog.getInt) - double類型數(shù)據(jù)(方法
QInputDialog.getDouble) - 下拉列表框的條目(方法
QInputDialog.getItem)
我們這里采用方法getInt,設(shè)定默認(rèn)的玩家數(shù)和對局?jǐn)?shù)是3,2,并且設(shè)定其最小值均為1,最大值均為99;同時,QInputDialog.getInt窗口提供了一個增加/減少按鈕,設(shè)定該按鈕對于整數(shù)的增加/減少步長為1。
2 新版設(shè)計(jì):Version 3.0
2.1 設(shè)計(jì)思路
在新版本Version3中,我們打算使用微信小程序的形式實(shí)現(xiàn)黃金點(diǎn)小游戲。因?yàn)?,我們考慮到了游戲本身不復(fù)雜,我們游戲的亮點(diǎn)能在哪?最后,我們團(tuán)隊(duì)成員一致認(rèn)為本軟件開發(fā)需要做到“快、準(zhǔn)、狠”三點(diǎn):
- 快:游戲能夠便捷的啟動、結(jié)束,不讓用戶在等待中耗費(fèi)激情。這就要求程序是B/S結(jié)構(gòu),即網(wǎng)頁端的應(yīng)用或者微信小程序、支付寶小程序等以WEB瀏覽器作為客戶端最主要的應(yīng)用軟件。這種模式統(tǒng)一了客戶端,將系統(tǒng)功能實(shí)現(xiàn)的核心部分集中到服務(wù)器上。
- 準(zhǔn):準(zhǔn)確獲取用戶的信息并存儲游戲相關(guān)的數(shù)據(jù)。我們最開始還打算自己建立數(shù)據(jù)庫,設(shè)置用戶登錄注冊的界面,自行管理用戶的賬戶密碼等信息。然而,我們團(tuán)隊(duì)成員尚處于學(xué)習(xí)階段,萬一數(shù)據(jù)庫管理不當(dāng)丟失或者被黑客攻擊,這不僅對我們游戲的運(yùn)行造成影響,更重要的是暴露的用戶的隱私。于是,后來我們想到借助騰訊微信大團(tuán)隊(duì)開發(fā)的穩(wěn)定安全的平臺,用戶通過微信賬號登錄游戲,我們用過微信小程序API接口存取用戶的個人信息與游戲信息,整個數(shù)據(jù)庫雖然由我們設(shè)計(jì),但是存儲保護(hù)都是依托微信平臺,安全、準(zhǔn)確、放心。
- 狠:所謂“狠”,也許有點(diǎn)抽象,我們想的就是要讓用戶喜歡上我們的游戲,舍不得離開,甚至愿意花費(fèi)一點(diǎn)點(diǎn)費(fèi)用在游戲中。但是我們只是一個簡簡單單甚至很枯燥的黃金點(diǎn)小游戲,怎么才能努力做到這點(diǎn)呢?這讓我想到了幾年前在微信平臺爆火的“打飛機(jī)”和“跳一跳”等無比簡單的小游戲,這些游戲都是無比簡單但是當(dāng)時卻特別火爆,為什么呢?很顯然是微信的“好友效應(yīng)”,即我的好友玩了,他們還邀請我挑戰(zhàn)我,我為了比他們有更高的分?jǐn)?shù)或者與他們互動,于是積極地進(jìn)入了游戲,于是這樣形成了一個游戲玩家的良性循環(huán),大家都有越來越高的熱情來玩。簡單來說,某種意義上,游戲越簡單,潛在玩家基數(shù)越大;再加上類似微信平臺的好友效應(yīng),玩家數(shù)量就有可能遠(yuǎn)遠(yuǎn)超出預(yù)期。
2.2 初次需求分析要點(diǎn)
對于該微信小程序的開發(fā),我們根據(jù)之前的設(shè)計(jì)思路,從一些受歡迎程度高的小程序入手,啟發(fā)思路,大致形成了以下的新的需求。
- 微信小程序用戶登錄后發(fā)起游戲,然后可以設(shè)定開始的游戲局?jǐn)?shù)和人數(shù)。
- 游戲普通玩家掃描二維碼就可以進(jìn)行登錄自己的微信號,然后輸入自己的數(shù)字進(jìn)行比賽。
- 在玩家個人界面,可以查看個人曾經(jīng)輸入的數(shù)字,相當(dāng)于一個歷史記錄的展示,以及每一局黃金點(diǎn)的曲線變動折線圖,這樣便可以看到本游戲中數(shù)學(xué)博弈原理的變化趨勢。
2.3 程序?qū)崿F(xiàn)流程
對于上面的簡單需求分析,我們接著進(jìn)行進(jìn)一步的研究和繼續(xù)細(xì)化,一步一步確定該微信小程序的設(shè)計(jì)流程,如下圖所示。但要明確的是,這只是我們的總體需求方向,不可避免我們在后續(xù)的開發(fā)迭代過程中會遇到新的變更和新的需求,這些都會影響和改變我們目前的初步設(shè)計(jì)。

3 設(shè)計(jì)小結(jié)
在兩周的設(shè)計(jì)過程中,實(shí)踐上,我們主要進(jìn)行了之前游戲開發(fā)版本的迭代開發(fā),對于Qt實(shí)現(xiàn)的UI界面進(jìn)行了改進(jìn)和優(yōu)化,但是由于該版本不適合新的用戶需求和設(shè)計(jì),經(jīng)過嚴(yán)密的分析和思考之后,我們決定采用微信小程序?yàn)橹饕问竭M(jìn)行繼續(xù)地迭代開發(fā)。
同時,我們也從理論上再次回顧結(jié)合了軟件工程理論課程上的知識,其中包括需求工程、設(shè)計(jì)工程、各種UML圖的表示與繪制等等,在這里僅展示出我們對于活動圖這一概念的著重理解和實(shí)踐體會。
活動圖,用于表示系統(tǒng)中各種活動的次序,它的應(yīng)用非常廣泛,即可用來描述用例的工作流程,也可用來描述類中某個方法的操作行為。常用于表示業(yè)務(wù)流程,對系統(tǒng)功能建模,強(qiáng)調(diào)對象之間的控制流?;顒訄D是由狀態(tài)圖變化而來的,活動圖依據(jù)對象狀態(tài)的變化來捕獲動作?;顒訄D中一個活動結(jié)束后將立即進(jìn)入下一個活動,狀態(tài)圖中狀態(tài)的變遷可能需要事件的觸發(fā)。主要用于系統(tǒng)功能建模。
需求設(shè)計(jì)過程中,我們知道,類模型體現(xiàn)了系統(tǒng)的靜態(tài)結(jié)構(gòu),用例模型則從用戶的角度對系統(tǒng)的動態(tài)行為進(jìn)行了宏觀建模,并通過交互模型將對象與消息有機(jī)地結(jié)合在一起。但有些時候,我們還需要更好地表示行為的細(xì)節(jié),這就可以借助于活動圖和狀態(tài)圖來實(shí)現(xiàn)。
而另一方面,普通活動圖雖然明確地說明了整個控制流的過程,但是卻沒有說明每個活動是由誰做的。對應(yīng)到編程而言,就是沒有明確地表示出每個活動是由什么類來負(fù)責(zé)的;對應(yīng)到業(yè)務(wù)建模,就是沒有明確地表示出機(jī)構(gòu)中的哪一個部門負(fù)責(zé)實(shí)施什么操作。為了在簡單活動圖的基礎(chǔ)上,有效地表示各個活動由誰負(fù)責(zé)的信息,還可以通過泳道圖來實(shí)現(xiàn)。但這些圖都各有其優(yōu)勢和發(fā)揮作用,下面是我們對于兩者的比較理解。
- 活動圖VS傳統(tǒng)流程圖:程序流程圖明確地指定了每個活動的先后順序,而活動圖僅描述了活動和必要的工作順序,這是兩者的根本區(qū)別。另外,流程圖限于順序進(jìn)程,而活動圖支持并發(fā)進(jìn)程。
- 活動圖VS狀態(tài)圖:狀態(tài)圖注重于由事件驅(qū)動的系統(tǒng)的變化狀態(tài);活動圖注重于從活動到活動的控制流?;顒訄D是狀態(tài)機(jī)的一種特殊情況,其中全部或大多數(shù)狀態(tài)是活動狀態(tài),并且全部或大多數(shù)轉(zhuǎn)換時通過源狀態(tài)中活動的完成來觸發(fā)的。活動圖適應(yīng)狀態(tài)機(jī)的全部特征?;顒訄D和狀態(tài)圖在對一個對象的生命周期建模時都是有用的。