如何设计和生成游戏的激活码

游戏的激活码,也叫作奖励码、兑换码,通常是由字符和数字组成的字符串,用于在游戏的推广阶段发放给玩家,玩家在下载登录游戏之后兑换获得相应的奖励。

首先设计我们激活码的规则

  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表示的数值范围中随机出一个值,这个值重复的概率是很低的。如果不是特别要求,可以忽略不计。

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值