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

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

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

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

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

E:排行榜是全服共享的

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

设计思路:

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

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

 

详细设计:

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

  游戏奖励表的设计:

 

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

 

  rank表的设计:

 

 

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)
    {
        if (null === $value) {
            $value = $this->getMyFieldStats($year, $uid, $field);
        }

        $where = array(
            'this_year' => $year,
            $field      => array('>', $value)
        );

        $rank = $this->where($where)->fetchCount();  // 这里是一个比较新颖的做法,得到我的排行

        return $rank + 1;
    }

 

注意并列排名的问题:

    /**
     * 对列表数组标记排位序号(支持并列排位)
     *
     * @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');
    }

 

给玩家发放奖励:

在游戏中,我们经常需要给玩家发放奖励,所以我们把发奖励这样的一个操作作为了独立的类封装起来。这样的做法,避免了很多代码的重复。在我们游戏奖励的格式是key=>value  例如,有一种奖励是: 100个金币和200个银币  award = 100:glod; 200:silver;

在我们的游戏中,有两种发奖方式: 按照权重发奖,和发指定的所有的奖励。

按照权重发奖:

    /**
     * 按权重发放其中一种奖励
     *
     * @param string $arrStr
     * @return array
     */
    public function sendOneByWeight($awardStr)
    {
        $return = array();

     // 解析奖励的权重
if (! $awardConfig = self::decodeAwardString($awardStr)) { return $return; } // 按权重命中一种奖励 $awardKey = Helper_Probability::hitByWeightField($awardConfig, 'weight'); // 执行发奖 $return = $this->__sendProcess($awardConfig[$awardKey]); return $return; }

 

解析奖励的权重:

    /**
     * 解析奖励配置字符串
     *
     * @param string $arrStr 格式:奖励类型:ID:数量:权重;...
     * @return array
     */
    public static function decodeAwardString($arrStr)
    {
        if (! $arrStr || ! $arrStr = explode(';', $arrStr)) {
            return array();
        }

        $return = array();

        foreach ($arrStr as $str) {

            $strs = explode(':', $str);

            // 奖励类型
            $awardType = $strs[0];

            // 1:银币 2:金块 3:声望 4:角色经验 5:船只经验 6:行动力 7:精力 8:HP 9:属性点
            if ($awardType >= 1 && $awardType <= 9) {
                $uniqId = null;
                $amount = isset($strs[1]) ? $strs[1] : null;
                $weight = isset($strs[2]) ? $strs[2] : null;
            }
            // 其他类型
            else {
                $uniqId = isset($strs[1]) ? $strs[1] : null;
                $amount = isset($strs[2]) ? $strs[2] : null;
                $weight = isset($strs[3]) ? $strs[3] : null;
            }

            $return[] = array(
                'type'    => $awardType,
                'uniq_id' => $uniqId,
                'amount'  => $amount,
                'weight'  => $weight,
            );
        }

        return $return;
    }

 

按照权重获取命中率:

 

    public static function hitByWeightField($probArr, $weightField = 'weight')
    {
        if (count($probArr) == 1) {
            return key($probArr);
        }

        // 概率数组的总概率精度
        $proSum = Helper_Array::sum($probArr, $weightField);

        // 概率数组循环
        foreach ($probArr as $key => $value) {
            $weight = $value[$weightField];
            $randNum = mt_rand(1, $proSum);
            if ($randNum <= $weight) {
                return $key;
            }
            $proSum -= $weight;
        }

        return false;
    }

 

执行发放奖励:

 

 /**
     * 内部方法:执行发奖
     *
     * @param array $award
     * @return mixed
     */
    private function __sendProcess(array $award)
    {
        // 返回结果集
        $return = array();

        switch ($award['type']) {

            // 银币
            case 1:

                $this->_user->base->addSilver($award['amount']);
                $return['message'] = ICO_SILVER . $award['amount'];

                break;

            // 金块
            case 2:

                $this->_user->base->addGold($award['amount']);
                $return['message'] = ICO_GOLD . $award['amount'];

                break;

            // 声望
            case 3:

                $this->_user->base->addReputation($award['amount']);
                $return['message'] = ICO_REPT . $award['amount'];

                break;

            // 主人公经验
            case 4:

                $this->_user->base->addExp($award['amount']);
                $return['message'] = ICO_EXP . $award['amount'];

                break;

            // 舰队船只经验
            case 5:

                $this->_user->ship->addFleetExp($award['amount']);
                $return['message'] = $award['amount'] . _('船只EXP');

                break;

            // 行动力
            case 6:

                $this->_user->restore->updateWithOffset(array('move' => $award['amount']));
                $return['message'] = ICO_MOVE . $award['amount'];

                break;

            // 精力
            case 7:

                $this->_user->restore->updateWithOffset(array('energy' => $award['amount']));
                $return['message'] = ICO_ENERGY . $award['amount'];

                break;
}
posted on 2013-07-03 15:13  孙力  阅读(2533)  评论(0编辑  收藏  举报