1. 程式人生 > >如何設計和生成遊戲的啟用碼

如何設計和生成遊戲的啟用碼

遊戲的啟用碼,也叫作獎勵碼、兌換碼,通常是由字元和數字組成的字串,用於在遊戲的推廣階段發放給玩家,玩家在下載登入遊戲之後兌換獲得相應的獎勵。

首先設計我們啟用碼的規則

  1. 字元 + 數字 組成 長度待定
  2. 啟用碼分批次或者叫分組,即一個批次/一組啟用碼對應一個禮包
  3. 同一批次/同一組的啟用碼兌換時有以下兩種設計:
    • 玩家可多次兌換
    • 玩家僅可兌換一次
  4. 啟用碼不區分大小寫

定義一個字元字典

#define DICT_SIZE 32
const char AwardCodeDict[DICT_SIZE] = { 'A','B','C','D','E','F','G','H','J',
                                        'K'
,'M','N','P','Q','R','S','T','U', 'V','W','X','Y','Z', '1','2','3','4','5','6','7','8','9' };

去掉辨識度比較低的字元 I 和 L, O 和 0
ps:數字1也易混淆,但我們這裡是沒有去掉的

如何去構造一個啟用碼
首先我們要考慮把哪些資訊存入到啟用碼中,這些資訊最終是要能夠被解析出來的。這裡我們存入的是禮包ID,在玩家兌換啟用碼時,我們通過解析這個啟用碼獲得的禮包ID給玩家發放獎勵。除了禮包ID我們還需要一些隨機碼,隨機碼的作用是為了讓啟用碼看起來更加具有隨機性,不容易被破解。那麼如何去存入這些資訊? 通過 碼值的移位操作

舉一個例子來說明

int main()
{
    int val = 0;
    int a = 1;  //               1
    int b = 2;  // 2 << 2     1000
    int c = 3;  // 3 << 4   110000

                //          111001              a,b,c |操作       ==> 57 

                //          111001 & 000011     第一次&操作      ==> 1
                // 57 >> 2  1110   & 000011                     ==> 2
// 14 >> 2 11 & 000011 ==> 3 val = a | (b << 2) | (c << 4); printf("%d\n",val); while(val) { int num = val & 3; // 3的二進位制 11 printf("%d\n",num); val = val >> 2; } system("pause"); return 0; }

執行結果

這裡先通過a,b,c依次向左等差移位2,作 | 操作得到值57,其二進位制111001從右向左每兩位代表一個數值。利用&操作的特性,再依次向右等差移位2,便可依次解析出來a,b,c。ps:移位間隔由最大數值的二進位制位數決定,這裡如果想存入數值4,那麼移位間隔就需要調整為3,相&的值也應調整為7(二進位制111)。

如此,啟用碼的禮包ID和隨機碼也利用這樣的原理去存入和解析。略有不同的是,禮包ID和隨機碼我們並不直接去存入,我們只存入禮包ID和隨機碼的索引,也就是AwardCodeDict的key值。這樣,啟用碼的構造基本就出來了,通過字典的key值移位和 | 操作得到啟用碼的碼值,再通過解碼操作,最終我們就能得到一個啟用碼了。

生成碼值
AwardCodeDict的key值最大為31,所以我們的移位間隔定義為5,相&的值為0x1F。隨機碼的個數我們限定為7個。

typedef unsigned long long  UINT64;

#define AWARD_CODE_BIT 5
#define AWARD_CODE_NUM 7

UINT64 GenerateAwardCodeVaule(UINT64 awardId)
{
    UINT64 codeVal = 0;
    for(int i = 0; i < AWARD_CODE_NUM; i++)
    {
        UINT64 key = rand() % DICT_SIZE;    
        codeVal |= key << (AWARD_CODE_BIT * i);
    }
    codeVal |= awardId << (AWARD_CODE_BIT * AWARD_CODE_NUM);
    return codeVal;
}

這裡需要特別留心awardId和key的型別定義,一定按最大的精度來定義,否則會導致codeVal部分資料丟失,最終解碼錯誤。<把key定義成int即可試驗>

解析碼值,生成啟用碼

int DecodeAwardCodeValue(UINT64 codeVal, char* code)
{
    int pos = 0;
    while(codeVal)
    {
        int key = codeVal & 0x1F;
        code[pos++] = AwardCodeDict[key];
        codeVal = codeVal >> AWARD_CODE_BIT;
    }
    return pos;
}

解析啟用碼,獲取碼值和禮包ID

// 不區分字元的大小寫
int GetKeyFromDict(char ch)
{
    int key = -1;
    for(int i = 0; i < DICT_SIZE; i++)
    {
        if(toupper(ch) == AwardCodeDict[i]){
            key = i;
            break;
        }
    }
    return key;
}

UINT64 GetAwardID(const char* code)
{
    if(!code || strlen(code) <= AWARD_CODE_NUM) return 0;

    UINT64 awardId = 0;
    for(int i = AWARD_CODE_NUM; i < strlen(code); i++)
    {
        UINT64 val = GetKeyFromDict(code[i]);
        awardId |= val << (AWARD_CODE_BIT * (i - AWARD_CODE_NUM));
    }
    return awardId;
}

UINT64 GetAwardCodeVaule(const char* code)
{
    if(!code) return 0;

    UINT64 codeVal = 0;
    for(int i = strlen(code) - 1; i >= 0; i--)
    {
        UINT64 key = GetKeyFromDict(code[i]);       
        codeVal |= key << (AWARD_CODE_BIT * i);
    }
    return codeVal;
}

最後寫一段執行程式,驗證上述方法

int main()
{
    srand((unsigned)time(NULL));

    while(1)
    {
        int awardId;
        char code[32] = {0};
        UINT64 codeVal = 0;

        printf("請輸入禮包ID:");
        cin >> awardId;

        for(int i = 0; i < 10; i++)
        {
            codeVal = GenerateAwardCodeVaule(awardId);
            DecodeAwardCodeValue(codeVal,code);
            printf("code:%s value:%llu  c2v:%llu c2k:%d\n",code,codeVal,GetAwardCodeVaule(code),GetAwardID(code));
            memset(code,0,32);
        }

    }

    system("pause");

    return 0;
}

執行結果

結尾總結
觀察啟用碼,可以發現同一批次/同一組/同一禮包的啟用碼後幾位是相同的。分析啟用碼的生成和解析過程,不難發現,生成碼值時我們是從低位到高位(從右向左的一個過程),生成啟用碼時我們依次 & 操作得到的也是 從低位到高位,而字串的讀取是從左向右,所以我們的禮包ID處於最高位,卻顯示在了字串的末尾。最後我們再來分析一下,這個啟用碼最大長度的問題。UINT64 8位元組 64bit,移位間隔5,所以有效長度為12,超過12就可能會出現部分資料丟失。那麼禮包ID最大有效值是多少呢?除去7個隨機碼佔用的位元位,剩下64 - 7*5 = 29bit,再除以移位間隔,那麼禮包ID的有效字元個數應該是5,最終可以得出禮包ID最大有效值應該是 11111 11111 11111 11111 11111 = 33554431。超過這個安全值之後,就不能保證一定能解碼成功。所以我們可以得出,啟用碼有效字元為12個字元,禮包ID有效最大值為33554431。由此,我們可以在生成和驗證啟用碼時加上保護判斷。

簡述啟用碼的兌換
這個過程,如果限定該禮包玩家只能兌換一次,只需記錄玩家ID和禮包ID(從啟用碼中獲得)即可。這裡使用redis資料庫最為便捷。把玩家ID和禮包ID以字串的形式存入到以xx為key的集合(Set)中。使用”SISMEMBER key member “即可查詢到玩家是否兌換過該禮包。啟用碼在被兌換或者使用後失效,則需要把該啟用碼從資料庫中刪除或者更新狀態,使用集合(Set)來儲存啟用碼也是非常方便的。

補充一點
生成的啟用碼會不會重複。從UINT64表示的數值範圍中隨機出一個值,這個值重複的概率是很低的。如果不是特別要求,可以忽略不計。