美文网首页Java游戏服务器编程
游戏排行榜系统的实现

游戏排行榜系统的实现

作者: 小圣996 | 来源:发表于2019-08-27 00:32 被阅读0次

    我的大刀早已饥渴难耐了 --蛮王

    游戏有很多的排行榜,如战力排行榜,等级排行榜,伤害排行榜,帮派排行榜,爬塔排行榜等,游戏的排行榜系统功能不难,很多人都知道如何实现,但是,不知萌新是否知道游戏所有排行榜系统其实是可以抽象出来,做成一个排行榜模板工具的。
    我们可以回忆总结一下游戏所有的排行榜功能,它们的核心属性有两个,一个是玩家的唯一id,一个是排名,因此可以把这两个属性封装成基类,其余的排行榜系统都可以由这个基类衍生而来,比如战力排行榜,则它可多加一个属性表示战力值的;等级排行榜,则它可多加一个属性表示等级的,还可加一个表经验值的属性,作为同等级的次级排序;伤害排行榜,则它可多加一个属性表示累积伤害值的。当我们对排行榜进行排序时,通过操作这个基类的排序对象与它的邻近对象进行compareTo对比,对比前提是这个基类的继承类都实现了Comparable接口,然后判断是大于还是小于从而决定该排序对象是否上升还是下浮,从而完成排序。就这样通过对基类的操作实现了所有的排行榜系统排序,而后增加其他的排行榜功能时,我们只需新建一个排序对象让它继承自这个排序基类就可以了,它的排序就可以不用管了,一劳永逸。

    不建议将玩家名字或玩家头像等作为这个基类的属性,因为很多游戏支持玩家改名,如果玩家名字改了,则排行榜上可能显示是之前的名字了。名字和头像这些可以直接根据玩家id去实时取。

    如果是帮派排行榜,这个唯一id,就是指帮派id了。

    综上,不难推出基类的实现为:
    BaseRankOrder.java

    package cn.xiaosheng996.rank;
    
    /**
     * 排行榜基类
     */
    public abstract class BaseRankOrder<T extends BaseRankOrder<T>> implements Comparable<T> {
        protected final long rid;// 唯一标志,rid
        protected int rank;// 排名
        //其余的等级,头像,名字等都需实时拿的,否则这些信息变化时还需更新排行榜里相应信息,没必要
        
        public BaseRankOrder(long rid) {
            this.rid = rid;
        }
        
        /**获取唯一id*/
        public long getRid(){
            return rid;
        }
        
        /**获取排名*/
        public int getRank(){
            return rank;
        }
        
        /**设置排名*/
        public void setRank(int rank){
            this.rank = rank;
        }
        
        /**比较 强制要求一定要实现*/
        public abstract int compareTo(T o);
    }
    

    接下来就是排行榜模板类的实现了,因为我们要为游戏里所有的排行榜功能统一做排序,所以这里用到了泛型实现;因为可能同时有多个线程提交和修改相应的排行榜,所以这里用到了同步容器和同步锁去实现排序;此外,如果对于一些数量巨大的排行榜而且名次还会下降上升浮动的,这里还支持按一定时间间隔去排序;其余的,对于一些数量不多的排序都可以实时去排,即将下面的checkAll属性设置为false即可。
    Sorter.java

    public class Sorter<T extends BaseRankOrder<T>> implements Runnable {
        protected final String name; //排行榜名字
        protected final int total; //总量
        protected final int period; //刷新间隔,为0表示实时排
        protected final boolean checkAll; //是否检查所有
        protected ConcurrentHashMap<Long, T> commits; //
        protected volatile Map<Long, T> orderMap; //
        protected volatile List<T> orderList; //
        
        protected static final Logger logger = Logger.getLogger(Sorter.class);
        
        public ScheduledExecutorService scheduleder() {
            return SingleInstanceHolder.scheduleder;
        }
    
        final private static class SingleInstanceHolder {
            final private static ScheduledExecutorService scheduleder = Executors
                    .newSingleThreadScheduledExecutor(new ThreadFactory() {
                        @Override
                        public Thread newThread(Runnable r) {
                            Thread t = new Thread(r);
                            t.setName("Sorter");
                            return t;
                        }
                    });
        }
    
        /**
         * 实例化
         * @param name 名称
         * @param total 容量
         * @param period 刷新时间间隔,0表示实时刷新
         * @param checkAll 是否需要每次提交数据都做全盘检查
         */
        public Sorter(String name, int total, int period, boolean checkAll) {
            this.name = name;
            this.total = total;
            this.period = period;
            this.checkAll = checkAll;
            this.commits = new ConcurrentHashMap<>();
            this.orderMap = new HashMap<>(total + 10);
            this.orderList = new ArrayList<>(total + 10);
            if (this.period > 0) {
                scheduleder().scheduleAtFixedRate(this, this.period, this.period, TimeUnit.SECONDS);
            }
            logger.fatal("创建排行榜 : " + this.toString());
        }
    
        public String getName() {
            return name;
        }
    
        public int getTotal() {
            return total;
        }
    
        public int getPeriod() {
            return period;
        }
    
        public boolean isCheckAll() {
            return checkAll;
        }
    
        public Map<Long, T> getCommits() {
            return commits;
        }
    
        /**
         * 清除排行榜
         */
        public void clear() {
            synchronized (this) {
                this.orderList.clear();
                this.orderMap.clear();
            }
        }
    
        /**
         * 当前容量
         */
        public int size() {
            return orderList.size();
        }
    
        /**
         * 安全备份有序数据
         */
        public List<T> safedList() {
            synchronized (this) {
                return new ArrayList<T>(this.orderList);
            }
        }
    
        /**
         * 安全备份无序数据
         */
        public Set<T> safedValues() {
            synchronized (this) {
                return new HashSet<T>(this.orderMap.values());
            }
        }
    
        public Set<Long> safedKeys() {
            synchronized (this) {
                return new HashSet<Long>(this.orderMap.keySet());
            }
        }
    
        /** 获取前top名 */
        public List<T> topList(int top) {
            return subList(0, top);
        }
    
        /**
         * 获取一个排名区间的成员
         * @param begin
         * @param end
         */
        public List<T> subList(int begin, int end) {
            final List<T> safedList = safedList();
            if (safedList.isEmpty()) {
                return new ArrayList<>();
            }
            if (begin < 0 || begin >= end || begin >= safedList.size()) {
                logger.error("获取排行榜[" + name + "]从[" + begin + "]到[" + end + "]的数据,size=" + size());
                return new ArrayList<>();
            }
            try {
                int e = end > safedList.size() ? safedList.size() : end;
                return (List<T>) safedList.subList(begin, e);
            } catch (Exception e) {
                logger.error("获取排行榜[" + name + "]从[" + begin + "]到[" + end + "]的数据,size=" + size(), e);
                return new ArrayList<>();
            }
        }
    
        /**
         * 每页num个可以分多少页
         * @param num 每页成员数
         */
        public int pages(int num) {
            final int length = size();
            if (length == 0) {
                return 0;
            }
            if (length <= num) {
                return 1;
            }
            return (length % num == 0) ? (length / num) : ((length / num) + 1);
        }
    
        /**
         * 按页显示
         * @param page 第几页
         * @param num 每页显示数量
         */
        public List<T> pageList(int page, int num) {
            final int pages = pages(num);
            if (page > pages || page < 1) {
                return new ArrayList<>();
            }
            final List<T> orderList = safedList();
            final List<T> resultList = new ArrayList<>(num);
            final int pageIndex = page - 1;
            for (int i = pageIndex * num; i < pageIndex * num + num; i++) {
                if (i >= orderList.size()) {
                    break;
                }
                resultList.add(orderList.get(i));
            }
            return resultList;
        }
    
        /**
         * 获取成员
         * @param id
         */
        public T getOrder(long rid) {
            return (T) orderMap.get(rid);
        }
    
        /**
         * 获取成员
         * @param rank 排名
         */
        public T getOrder(int rank) {
            if (rank < 1 || rank > orderList.size()) {
                return null;
            }
            return (T) orderList.get(rank - 1);
        }
    
        /**
         * 获取排名
         * 
         * @param id
         * @return
         */
        public Integer getRank(long id) {
            T order = getOrder(id);
            return order == null ? null : order.getRank();
        }
    
        /**
         * 提交成绩
         * @param order
         */
        public void commit(T order) {
            if (this.period == 0) {
                synchronized (this) {
                    this.submit(order);
                }
            } else {
                this.commits.put(order.getRid(), order);
            }
        }
    
        /**
         * 定时刷新执行批量提交数据操作
         */
        @Override
        public void run() {
            List<T> taskList = null;
            synchronized (this) {
                taskList = new ArrayList<>(this.commits.values());
                this.commits = new ConcurrentHashMap<>();
            }
            if (!taskList.isEmpty()) {
                taskList.forEach(e -> {
                    this.submit(e);
                });
            }
        }
    
        private int submit(T order) {
            return pushSort(order);
        }
    
        private int pushSort(T order) {
            try {
                if (!orderMap.containsKey(order.getRid())) {
                    addLast(order);
                    return pushUp(order);
                } else {
                    T old = orderMap.get(order.getRid());
                    if (checkAll) {// 全盘检查并且成绩下降
                        removal(order.getRid());
                        addLast(order);
                        return pushUp(order);
                    } else {//适合于只会递增的
                        int oldRank = old.getRank();
                        old = order;
                        old.setRank(oldRank);
                        return pushUp(old);
                    }
                }
            } finally {
                reset();
            }
        }
    
        private void addLast(T order) {
            synchronized (this) {
                orderMap.put(order.getRid(), order);
                orderList.add(order);
                order.setRank(orderList.size());
            }
        }
    
        private void reset() {
            synchronized (this) {
                for (int i = orderList.size(); --i >= total;) {
                    T rm = orderList.remove(i);
                    if (rm != null) {
                        orderMap.remove(rm.getRid());
                    }
                }
            }
        }
    
        /**
         * 从列表删除一条记录并且重新设置名次
         * @param id
         */
        public void removal(Long rid) {
            synchronized (this) {
                for (int i = orderList.size(); --i >= 0;) {
                    if (orderList.get(i).getRid() == rid) {
                        orderList.remove(i);
                        orderMap.remove(rid);
                        break;
                    }
                }
                for (int i = 0; i < orderList.size(); i++) {
                    orderList.get(i).setRank(i + 1);
                }
            }
        }
    
        /**
         * 向上推
         * @param order
         * @return
         */
        private int pushUp(T order) {
            int count = 0;
            int beforeIndex = order.getRank() - 2;
            for (int i = beforeIndex; i >= 0; i--) {
                T before = (T) orderList.get(i);
                int beforeRank = before.getRank();
                if (order.compareTo(before) >= 0) {
                    break;
                }
                order.setRank(beforeRank);
                before.setRank(beforeRank + 1);
                orderList.set(i, order);
                orderList.set(i + 1, before);
                count++;
            }
            return count;
        }
    
        /**
         * 向下推
         * @param order
         * @return
         */
        private int pushDown(T order) {
            int count = 0;
            int index = order.getRank();// 下一个
            for (int i = index; i < orderList.size(); i++) {
                T next = (T) orderList.get(i);
                int nextRank = next.getRank();
                if (next.compareTo(order) >= 0) {// 比自己小,跳出
                    break;
                }
                // 比自己大,换位
                order.setRank(nextRank);
                next.setRank(nextRank - 1);
                orderList.set(i - 1, next);
                orderList.set(i, order);
                count++;
            }
            return count;
        }
    
        /**
         * 强制把所有数据立刻进行排序
         */
        public void flush() {
            this.run();
        }
    
        @Override
        public String toString() {
            return "Sorter[名称:" + name + ",最大容量:" + total + ",刷新时间:" + period + ",是否检查成绩下降:" + checkAll + "]";
        }
    }
    

    这样,就可以为所有的排行系统排序了,接下来可以做个排行榜管理类,用于统一创建和管理这些排行榜系统,实现如下:
    SorterManager.java

    /**
     * 排行榜管理器
     */
    public class SorterManager {
    
        /**
         * 排行榜容器
         */
        @SuppressWarnings("rawtypes")
        public static final Map<String, Sorter> ALLSORTERS = new ConcurrentHashMap<>();
    
        /**
         * 获取不存在就创建排行榜
         * 
         * @param name 名称
         * @param total 总容量,不限量可写个尽量大的值
         * @param period 刷新时间-秒0表示实时刷新
         * @param checkAll 是否需要全部检查一次:对于成员的成绩会下降导致排名大幅波动就需要设置为true,每次提交成绩都从最底下开始往上检查
         * @return
         */
        @SuppressWarnings("unchecked")
        public static <T extends BaseRankOrder<T>> Sorter<T> getOrCreateSorter(String name, int total, int period,
                boolean checkAll) {
            if (!ALLSORTERS.containsKey(name)) {
                ALLSORTERS.put(name, new Sorter<T>(name, total, period, checkAll));
            }
            return ALLSORTERS.get(name);
        }
    
        /**
         * 获取不存在就创建排行榜.默认最多存200个成员,实时刷新,做整体检查
         * @param name 名称
         * @return
         */
        public static <T extends BaseRankOrder<T>> Sorter<T> getOrCreateSorter(String name) {
            return getOrCreateSorter(name, 200, 0, false);
        }
    
        /**
         * 获取不存在就创建排行榜.默认最多存100个成员,实时刷新,做整体检查
         * @param name 名称
         * @param total 容量
         * @return
         */
        public static <T extends BaseRankOrder<T>> Sorter<T> getOrCreateSorter(String name, int total) {
            return getOrCreateSorter(name, total, 0, false);
        }
    }
    

    至此,这个排行榜工具就实现了,举个使用栗子:
    比如,我们要实现一个副本排行榜,达到最大关卡排序,如果相同关卡则按谁先到达时间排序,我们可以这样添加一个排序基类的实现类:

    public class CopyRank extends BaseRankOrder<CopyRank> {
        protected int copyId;
        protected int reachTime;
        
        public CopyRank(long rid, int copyId, int reachTime) {
            super(rid);
            this.copyId = copyId;
            this.reachTime = reachTime;
        }
    
        @Override
        public int compareTo(CopyRank o) {
            if (this.copyId == o.copyId){
                return this.reachTime - o.reachTime;
            }else {
                return o.copyId - this.copyId;
            }
        }
    
        public int getCopyId() {
            return copyId;
        }
    
        public void setCopyId(int copyId) {
            this.copyId = copyId;
        }
    
        public int getReachTime() {
            return reachTime;
        }
    
        public void setReachTime(int reachTime) {
            this.reachTime = reachTime;
        }
    }
    

    就写这么点就差不多了,一天写30个排行榜功能也不在话下,我们可以测试一番:

        public static void main(String[] args){
            //副本排行榜
            Sorter<CopyRank> sequencer = SorterManager.getOrCreateSequencer("CopyRank");
            
            for (int i = 1; i <= 500; i++) {
                long rid = Probability.rand(1, 300); //比如有300个玩家
                CopyRank rank = sequencer.getOrder(rid);
                if (rank == null){
                    int initCopyId = Probability.rand(1, 5);
                    int initTime = Probability.rand(1, 100);
                    rank = new CopyRank(rid, initCopyId, initTime);
                } else{
                    int newCopyId = rank.getCopyId() + Probability.rand(1, 5);
                    int newTime = rank.getReachTime() + Probability.rand(1, 5);
                    rank.setCopyId(newCopyId);
                    rank.setReachTime(newTime);
                }
                sequencer.commit(rank);
            }
            
            sequencer.safedList().forEach(e -> {
                System.out.println("rid:" + e.getRid() + ", rank:" + e.getRank() + ", maxCopyId:" + e.getCopyId() + ",reachTime:" + e.getReachTime());
            });
        }
    

    结果输出如下:

    rid:96, rank:1, maxCopyId:27,reachTime:87
    rid:232, rank:2, maxCopyId:20,reachTime:35
    rid:196, rank:3, maxCopyId:20,reachTime:84
    rid:88, rank:4, maxCopyId:19,reachTime:79
    rid:168, rank:5, maxCopyId:18,reachTime:12
    rid:143, rank:6, maxCopyId:17,reachTime:95
    rid:35, rank:7, maxCopyId:16,reachTime:48
    rid:191, rank:8, maxCopyId:14,reachTime:13
    rid:21, rank:9, maxCopyId:14,reachTime:16
    rid:101, rank:10, maxCopyId:14,reachTime:81
    rid:192, rank:11, maxCopyId:13,reachTime:37
    rid:27, rank:12, maxCopyId:13,reachTime:55
    rid:80, rank:13, maxCopyId:13,reachTime:104
    rid:161, rank:14, maxCopyId:12,reachTime:11
    rid:116, rank:15, maxCopyId:12,reachTime:15
    rid:62, rank:16, maxCopyId:12,reachTime:28
    rid:158, rank:17, maxCopyId:12,reachTime:29
    rid:165, rank:18, maxCopyId:12,reachTime:68
    rid:2, rank:19, maxCopyId:12,reachTime:83
    rid:300, rank:20, maxCopyId:12,reachTime:84
    rid:299, rank:21, maxCopyId:12,reachTime:84
    rid:279, rank:22, maxCopyId:12,reachTime:94
    rid:230, rank:23, maxCopyId:11,reachTime:13
    rid:76, rank:24, maxCopyId:11,reachTime:25
    rid:87, rank:25, maxCopyId:11,reachTime:40
    rid:54, rank:26, maxCopyId:11,reachTime:45
    rid:228, rank:27, maxCopyId:11,reachTime:62
    rid:10, rank:28, maxCopyId:11,reachTime:82
    rid:202, rank:29, maxCopyId:11,reachTime:101
    rid:118, rank:30, maxCopyId:10,reachTime:16
    ……

    妥妥地ok了......

    注意,这里只是实现,相应的排行榜初始化还需读者自己完成,它可以在服务器启动时从数据库加载初始化,类似如下:

      private void initCopyRank() {
           String sql = null;
           try {
              //后面加数量,以时间换空间,避免开服时占用太多内存,给copyId建索引,提高检索效率
              sql = "select t.rid, t.copyId, t.time from copy t order by t.copyId desc limit 50";
              List<Map<String, Object>> rankList = getDB().query(sql);
              for (Map<String, Object> e : rankList) {
                  long rid = (long) e.get("rid");
                  int maxCopyId = (int) e.get("copyId");
                  int time = (int) e.get("time");
    
                  CopyRank order = new CopyRank(rid, maxCopyId, time);
                   copySorter.commit(order);
               }
           } catch (Exception e) {
               logger.error("初始化副本排行榜出错!", e);
           }
       }
    

    相关文章

      网友评论

        本文标题:游戏排行榜系统的实现

        本文链接:https://www.haomeiwen.com/subject/oullsctx.html