游戏排行榜系统的实现

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

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

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

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

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

package cn.xiaosheng996.rank;

/**
 * 排行榜基类
 */
public abstract class BaseRankOrder> implements Comparable {
    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> implements Runnable {
    protected final String name; //排行榜名字
    protected final int total; //总量
    protected final int period; //刷新间隔,为0表示实时排
    protected final boolean checkAll; //是否检查所有
    protected ConcurrentHashMap commits; //
    protected volatile Map orderMap; //
    protected volatile List 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 getCommits() {
        return commits;
    }

    /**
     * 清除排行榜
     */
    public void clear() {
        synchronized (this) {
            this.orderList.clear();
            this.orderMap.clear();
        }
    }

    /**
     * 当前容量
     */
    public int size() {
        return orderList.size();
    }

    /**
     * 安全备份有序数据
     */
    public List safedList() {
        synchronized (this) {
            return new ArrayList(this.orderList);
        }
    }

    /**
     * 安全备份无序数据
     */
    public Set safedValues() {
        synchronized (this) {
            return new HashSet(this.orderMap.values());
        }
    }

    public Set safedKeys() {
        synchronized (this) {
            return new HashSet(this.orderMap.keySet());
        }
    }

    /** 获取前top名 */
    public List topList(int top) {
        return subList(0, top);
    }

    /**
     * 获取一个排名区间的成员
     * @param begin
     * @param end
     */
    public List subList(int begin, int end) {
        final List 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) 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 pageList(int page, int num) {
        final int pages = pages(num);
        if (page > pages || page < 1) {
            return new ArrayList<>();
        }
        final List orderList = safedList();
        final List 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 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 ALLSORTERS = new ConcurrentHashMap<>();

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

    /**
     * 获取不存在就创建排行榜.默认最多存200个成员,实时刷新,做整体检查
     * @param name 名称
     * @return
     */
    public static > Sorter getOrCreateSorter(String name) {
        return getOrCreateSorter(name, 200, 0, false);
    }

    /**
     * 获取不存在就创建排行榜.默认最多存100个成员,实时刷新,做整体检查
     * @param name 名称
     * @param total 容量
     * @return
     */
    public static > Sorter getOrCreateSorter(String name, int total) {
        return getOrCreateSorter(name, total, 0, false);
    }
}

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

public class CopyRank extends BaseRankOrder {
    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 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> rankList = getDB().query(sql);
          for (Map 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);
       }
   }

你可能感兴趣的:(游戏排行榜系统的实现)