背景
使用Qt5.12.9的QGraphicsItem來實現(xiàn)俄羅斯方塊,使用Sqlit3存儲數(shù)據(jù)來進行游戲的回放,既然已經(jīng)使用QT,就盡量用其組件,重寫了原來的JSON封裝及數(shù)據(jù)庫操作接口實現(xiàn)。
思路
盡量復用已經(jīng)實現(xiàn)的代碼,所以只記錄了每個方塊的形狀與姿態(tài)(旋轉次數(shù))及最終位置。與真實游戲的區(qū)別只是在于方塊的來源一個是隨機生成,一個是數(shù)據(jù)庫。記錄的ID我引用了原來使用的snowflake模型,但卻使用了QString類型,無它,只是長整型在編程的過程中總是不好控制,莫名的被改成科學計數(shù)法或溢出。數(shù)據(jù)庫操作使用C++對sqlit3庫進行封裝并結合Qjson實現(xiàn)ORM自動操作。Sqlit3的動態(tài)鏈接庫,在windows、linux、mac各平臺下需要重新編譯。
效果圖

關鍵代碼分析
cmake之操作系統(tǒng)選擇
if(MSVC) #windows
set(EnclibName Sqlit3.dll)
elseif(APPLE) #mac
set(EnclibName sqlite3.o)
elseif(UNIX) #linux
set(EnclibName libsqlite3.so)
endif()
Qjson.h - json操作封裝
我的數(shù)據(jù)操作實現(xiàn)中的信息傳遞,都是以json對象形式進行的,封裝QJsonObject、QJsonArray等,提供方便的json操作接口,實現(xiàn)參數(shù)傳遞與SQL自動生成。我的實現(xiàn)Object是一等公民,Array是二等公民只作為Qjson的一個子項。這樣既能滿足應用又把設計大大的簡化了,規(guī)范使用,避免誤用。
class Qjson {
private:
QJsonObject* json; //真實的json對象,我只是對其封裝,簡化操作或轉化成簡單的操作
bool _IsObject_; //是否是正確的json對象,構造函數(shù)中對其進行判斷,表示json對象的有效性
public:
Qjson();
Qjson(const Qjson& origin); //復制構造函數(shù)
QString operator[](QString key); //下標操作,取特定key對應的值,使用QString返回,可以方便進行類型轉換
Qjson& operator = (const Qjson& origin); //賦值構造函數(shù)
bool HasMember(QString key) ;
Qjson ExtendObject(Qjson obj); //合并兩個Object,第一個中同名key會被覆蓋
template<typename T> void AddValueBase(QString k, T v); //key - value 插入模板函數(shù),基礎類型插入它就能搞定
void AddValueObjectsArray(string k, QVector<Qjson>& arr) ; //Array類型值插入
QString GetJsonString(); //取得json的字符串
void GetValueAndTypeByKey(QString key, QString* v, int* vType); //取值的真正操作函數(shù)
QStringList GetAllKeys(); //取得json對象的所有key,用于對象遍歷
bool IsObject();
private:
QJsonObject* GetOriginRapidJson(); //取得真實的json對象,類內部使用
};
數(shù)據(jù)庫通用接口 - Idb.h
經(jīng)實踐總結,數(shù)據(jù)庫操作有以下接口,百分之九十以上的需求就都能滿足。
class Idb
{
public:
virtual Qjson select(QString tablename, Qjson& params, QStringList fields = QStringList(), int queryType = 1) = 0;
virtual Qjson create(QString tablename, Qjson& params) = 0;
virtual Qjson update(QString tablename, Qjson& params) = 0;
virtual Qjson remove(QString tablename, Qjson& params) = 0;
virtual Qjson querySql(QString sql, Qjson params = Qjson(), QStringList filelds = QStringList()) = 0;
virtual Qjson execSql(QString sql) = 0;
virtual Qjson insertBatch(QString tablename, QVector<Qjson> elements, QString constraint = "id") = 0;
virtual Qjson transGo(QStringList sqls, bool isAsync = false) = 0;
};
數(shù)據(jù)庫操作標準實現(xiàn) - DbBase.h
我們的上層應用都是操作這個實現(xiàn),這個類組合了一個Idb的具體實現(xiàn),從而達到與具體的數(shù)據(jù)庫解耦的目的,可以輕松的切換不同的數(shù)據(jù)庫實例。
class DbBase
{
public:
DbBase(QString connStr, QString dbType = "sqlit3") : connStr(connStr) {
dbType.toLower();
if (dbType.compare("sqlit3") == 0)
db = new Sqlit3::Sqlit3Db(connStr);
else {
throw "Db Type error or not be supported. ";
}
};
...
};
Sqlit3的C++封裝類 - Sqlit3Db.h
實現(xiàn)了Sqlit3的Idb接口,下面抽了幾個關鍵點進行一些說明:
std::unique_ptr
數(shù)據(jù)連接使用了std::unique_ptr,在gcc下一定要引入 memory 頭文件,這個問題折騰了我好久,在windows下沒出問題,但在linux下一直報錯。
sql語句中中文的支持
要保證送入底層的sql語句的編碼為Utf-8,為了保證對所有操作系統(tǒng)的支持,選擇使用QString::fromUtf8(szU8)來進行轉換。
Qjson ExecNoneQuerySql(QString aQuery) {
Qjson rs = Utils::MakeJsonObjectForFuncReturn(STSUCCESS);
sqlite3_stmt* stmt = NULL;
sqlite3* handle = getHandle();
char * u8Query = Utils::UnicodeToU8(aQuery); //編碼轉換,函數(shù)中的主要功能由QString::fromUtf8完成
const int ret = sqlite3_prepare_v2(handle, u8Query, strlen(u8Query), &stmt, NULL);
if (SQLITE_OK != ret)
{
QString errmsg = sqlite3_errmsg(getHandle());
rs.ExtendObject(Utils::MakeJsonObjectForFuncReturn(STDBOPERATEERR, errmsg));
}
else {
sqlite3_step(stmt);
}
sqlite3_finalize(stmt);
qDebug() << "SQL: " << aQuery << endl; //日志輸入使用未轉換的
return rs;
}
批量插入操作
游戲記錄必須使用批量插入操作,不能一條一條插入。sqlit3的批量操作與mysql有些不同,用的是select ... union all,其實也只是按規(guī)定作好SQL語句拼接就可以了。
Qjson insertBatch(QString tablename, QVector<Qjson> elements, QString constraint) {
QString sql = "insert into ";
if (elements.empty()) {
return Utils::MakeJsonObjectForFuncReturn(STPARAMERR);
}
else {
QString keyStr = " (";
keyStr.append(elements[0].GetAllKeys().join(',')).append(" ) "); //取出參數(shù)第一個元素的所有key,組裝數(shù)據(jù)庫字段
for (size_t i = 0; i < elements.size(); i++) {
QStringList keys = elements[i].GetAllKeys(); //取出參數(shù)的所有key,實現(xiàn)對json對象的遍歷
QString valueStr = " select ";
for (size_t j = 0; j < keys.size(); j++) {
valueStr.append("'").append(elements[i][keys[j]]).append("'");
if (j < keys.size() - 1) {
valueStr.append(",");
}
}
if (i < elements.size() - 1) { //拼接下一條記錄
valueStr.append(" union all ");
}
keyStr.append(valueStr);
}
sql.append(tablename).append(keyStr);
}
return ExecNoneQuerySql(sql);
}
JOSN-ORM的使用方法
數(shù)據(jù)查詢使用的智能的ORM實現(xiàn),具體方法請參照前文《c++關系數(shù)據(jù)庫訪問通用接口設計》
Playback 功能實現(xiàn)
回放設置了幾個參數(shù)
- last : 最后一次游戲的回放
- one :積分排行第一的游戲
- two :積分排行第二的游戲
- three :積分排行第三的游戲
源代碼及運行方法
項目采用cmake組織,請安裝cmake3.10以上版本。下面腳本是windows下基于MSVC的,其它操作系統(tǒng)上基本類似,或者使用qtcreator打開進行操作。
cmake -A win32 -Bbuild .
cd build
cmake --build . --config Release
注:本項目采用方案能跨平臺運行,已經(jīng)適配過windows,linux,mac。
源代碼:
https://gitee.com/zhoutk/qtetris.git
或
https://gitee.com/zhoutk/qtdemo/tree/master/tetrisGraphicsItem
或
https://github.com/zhoutk/qtDemo/tree/master/tetrisGraphicsItem