游戏中排行榜的设计

在游戏的设计中,我们需要设计一个贸易利润和综合实力排行榜。在排行榜中,我们有这样的需求:

A: 排行榜4天更新一次,也就是,需要统计玩家4天内贸易利润和综合实力

B: 上榜的玩家有奖励发送。

C: 综合实力是统计全服玩家,如果玩家分库存储,需要遍历所有的玩家库

D:4天后,删除旧数据,更新原来的数据。

E:排行榜是全服共享的

F:上榜的玩家的信息是动态的,如,玩家的等级,玩家的国籍(游戏中,可以允许玩家修改国籍)

G: 我的排名显示,和排行榜中的排名显示,要一致。

我们在做最后一个需求的时候,出现过这样的问题,排名榜是全服共享的,而我的排名是我从数据库中实时读出来的,也就是说,当玩家A第一次查看排行榜的时候,此时会缓存排行榜的数据1个小时,如果玩家B在过来看排行榜的时候,其实,这个排行榜是玩家A缓存的。但是,“我的排名”是从数据库实时读出来的,如果玩家B在榜上,而且已经被缓存了,那么如果玩家B的排名发生变化,且缓存还没有失效,那么玩家的的排名就会和排行榜上的有所不同。


解决做法:

   A 缓存我的排行榜和我的排名的数据。

   B 读取我的排名的时候,如果他在排行榜上,优先读取排行榜中的排名

   C 当更新排行榜的缓存的时候(即清空原来的缓存,然后缓存最新的信息),同时,清空 “我的排名” 的缓存。防止排行榜中的排名和我的实际排名不一致的问题

 还有一个最大的问题:

  排行榜的数据缓存1小时。每过4天后,重新统计排行榜的信息。也就是说,4天后,排行榜中的信息全部失效,重新统计,如果此时,被缓存的排行榜数据没有失效,那么,我读出来的排行榜的数据还是原来的数据。


怎么解决这个问题:

最初的想法是,直接删除原来缓存的数据,可是怎么删除呢,这个做法不可行。

最好的做法是,在缓存的key里面加入时间,如果时间过期,就不会读这个key对应的内容。


$cacheKey = 'Rank:' . $field . ':' .  $year;


设计思路:

   创建一个rank表,统计玩家4天内的贸易利润。

   创建一个奖励配置rank_award表,给上榜的玩家发放奖励。

 

详细设计:

  在设计rank表的时候,我们需要对“4”天这个时间段作限制。我们考虑的是,以某一个时间作为“标准”,计算4天后的时间,作为一个截止时间,也就是在这个截止时间内,统计玩家的获取的利润,到了这个时间点后,停止统计玩家获取的利润,同时,结算给玩家奖励,然后清除排行榜,同时重新统计玩家的信息。

  游戏奖励表的设计:

 游戏中排行榜的设计_第1张图片

award 的对应是: 1:100;2:200;这样的格式。

 

  rank表的设计:

 游戏中排行榜的设计_第2张图片

 

this_year  这个字段是一个截止时间的概念,在我们游戏中,有一个游戏年的概念,是4天作为一个游戏年,所以,我们正好使用这个游戏年,作为排行榜时间段。

这个this_year是这样被计算出来的:

游戏的开始元年是:                    startTime = 2013/1/1

当前游戏的天数:                     day =  max(1, ceil(time() - strtotime(startTime))/24*60*60);

当前游戏的年数(4天作为一年):  year =   str_pad(ceil(day/4), 4,0 str_pad_left); 

当前游戏的季节:                    season= (day%4) (0:春天 1:夏天 2:秋天 3:冬天)

这样做的目的,避免了每次插入用户的一条记录时,都需要做时间段的比对:

复制代码
    public static function incrYearlyRankStats($uid, $field, $step = 1)
    {
        $where = array(
            'this_year' => $year,
            'uid'       => $uid,
        );

        $result = $this->where($where)->increment($field, $step);

        // 更新影响行数为0
        if (! $result) {
            // 如果记录确实不存在,则插入一条新记录
            if (! $this->where($where)->fetchCount()) {
                // 执行插入
                $setArr = array(
                    'this_year' => $year,
                    'uid'       => $uid,
                    $field      => $step,
                );
                $this->insert($setArr);
            }
        }

        return true;
    }
复制代码

 

获取我的排名:

复制代码
    public function getMyRankNo($year, $uid, $field, $value = null)
    {
        $year = $year ?: $GLOBALS['_V_YEAR'];
        $cacheKey = 'Rank:' . $field . ':' .  $year;


        // 若我在排行榜上,则从排行榜中读取我的排名
        if ($list = F('Memcache')->get($cacheKey)) {
            foreach ($list as $rank) {
                if ($rank['uid'] == $uid) {
                    return $rank['rank_no'];
                }
            }
        }


        // 获取排行榜的列表
        $cacheKey = 'MyRankNoKey:' . $field . ':' . $year . ':' . $uid ;


        // 先从缓存中读取
        if ($myRankNo = F('Memcache')->get($cacheKey)) {
            return $myRankNo;
        }


        $myRankNo = Dao('Share_RankYearly')->getMyRankNo($year, $uid, $field);


        // 保存到缓存中
        F('Memcache')->set($cacheKey, $myRankNo, 3600);


        return $myRankNo;
    }
复制代码

 

注意并列排名的问题:

复制代码
    /**
     * 对列表数组标记排位序号(支持并列排位)
     *
     * @param array $topList 已降序排好的列表数组
     * @param string $scoreField 排名比较字段
     * @return void
     */
    public static function decorateRankNo(array $topList, $scoreField)
    {
        $rankNo = 1;
        $lastScore = 0;


        foreach ($topList as &$value) {

            if ($value[$scoreField] < $lastScore) {
                $rankNo++;
            }

            $lastScore = $value[$scoreField];

            $value['rank_no'] = $rankNo;
        }

        return $topList;
    }
复制代码

 

综合实力榜,涉及到玩家的分库问题,需要遍历所有的玩家库:

复制代码
    public static function getCombatPowerRank()
    {
        $list = array();

        // 遍历所有用户分库,执行清理
        for ($i = 1; $i <= DIST_USER_DB_NUM; $i++) {
            if ($distList = Dao('Dist_User')->setDs($i)->getCombatPowerTopUsers(self::RANK_LIMIT)) {
                $list = array_merge($list, $distList);   // 使用array_merge()做数组的合并
            }
        }

        // 重新排序
        $list = Helper_Array::multiSort($list, array(
            'combat_power' => SORT_DESC
        ));

        // 截取前N名
        $list = array_slice($list, 0, self::RANK_LIMIT); // 使用array_slice()对数组进行切割

        // 对列表数组标记排位序号(支持并列排位)
        return self::decorateRankNo($list, 'combat_power');
    }
复制代码


你可能感兴趣的:(PHP,游戏设计与实现)