天梯匹配系统 - 简单实现

最近利用业余时间开发一个支持多人对战游戏的天梯匹配系统,纯粹练手之用。该天梯系统需要满足以下要求

1. 有单人对战和多人对战模式,例如从1v1到5v5

2. 每个人都有两个天梯分,分别是1v1的天梯分,和2v2或以上对战的天梯分

3. 每局匹配的最高分和最低分玩家分差不能超过设定值

4. 每局匹配双方间分差不能超过设定值

5. 每个人可和一名或多名好友组队共同参与天梯匹配,组队后系统将计算组内天梯值平均分并适量加成作为匹配依据

6. 成功的匹配必须在对战双方同时包含相同的黑店数量。

7. 玩家选择好对战模式后,开始参与天梯匹配,如果匹配成功则返回对阵双方的匹配结果,否则返回空

实现思路概要

1. 匹配池按照<分数,参与玩家/组队列表>的键值对建立红黑树

2. 每一个新玩家/组队参与进来,将会根据对战模式在天梯池寻找合适玩家

3. 匹配过程首先会在天梯池中,选择预选玩家数量 = 对战所需玩家数量 + 一定buffer的玩家数量,选取规则为以当前新玩家分值为起点,分别向高低分数交替选择剩余玩家,直到满足预选玩家数量或者最高分和最低分玩家分差超过设定值

4. 如果预选玩家数量小于对战所需玩家数量 ,则把已选取的玩家和新玩家重新放回匹配池并退出

5. 对预选玩家进行排序,排序依据为队伍大小和新玩家/组队一样,则优先。否则按分数接近程度排列

6. 根据排序结果依次把新玩家和预选玩家交替放入匹配结果的两队中

7. 如果没有在对战双方匹配到的对等的黑店数量,则视为匹配失败

8. 最后根据对战双方天梯分差进行有必要的位置对调,使得两队天梯分总分差最小。

最新代码可从https://github.com/loveisasea/dmatch.git下载

项目采用web形式,可通过http的json请求测试,postman的url为地址。当然也可使用JUnit等工具测试,这就不一一列出了。

示例

其中匹配核心算法如下

package com.fym.match;

import com.fym.core.err.OpException;
import com.fym.core.err.OpResult;
import com.fym.game.enm.GameType;
import com.fym.match.obj.IUnit;
import com.fym.match.obj.Match;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created by fengy on 2016/5/25.
 */
@Component
public class MatchEngine implements InitializingBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(MatchEngine.class);

    private Map<GameType, TreeMap<Integer, Queue<IUnit>>> matchPools = new HashMap<>();

    private final static int Max_Score_Diff = 100;

    private final static int Buffer_Match = 4;

    public synchronized  Match tryMatch(final IUnit mUnit, Integer gameTypeKey) throws OpException {
        return this.tryMatch(mUnit, GameType.get(gameTypeKey));
    }

    /**
     * 匹配函数
     * 思路:
     * 1. 先从参赛者分数震荡向高低两边取未匹配玩家和组队,需要符合最大分数差,而且选取数量带有一定buffer
     * 2. 把第一步选取出来的玩家按照规模大小接近和分值接近排序
     * 3. 从结果集中
     * @param mUnit
     * @param gameType
     * @return
     * @throws OpException
     */
    public synchronized Match tryMatch(final IUnit mUnit, GameType gameType) throws OpException {
        TreeMap<Integer, Queue<IUnit>> matchpool = this.matchPools.get(gameType);
        if (matchpool == null) {
            throw new OpException(OpResult.INVALID, "找不到该比赛类型" + gameType);
        }
        if (mUnit == null || mUnit.getSize() == 0) {
            throw new OpException(OpResult.FAIL, "匹配玩家或队伍不能为空");
        }
        if (gameType.pcnt < mUnit.getSize()) {
            throw new OpException(OpResult.FAIL, "队伍人数超过游戏类型人数限制");
        }

        int restCnt = gameType.pcnt * 2 - mUnit.getSize() + Buffer_Match; //需要挑出来匹配的玩家总数量
        List<IUnit> results = new ArrayList<>(restCnt); //初步挑出来匹配的玩家

        //上下分数匹配
        {
            Map.Entry<Integer, Queue<IUnit>> hEntry = matchpool.ceilingEntry(mUnit.getScore());
            Map.Entry<Integer, Queue<IUnit>> lEntry = matchpool.floorEntry(mUnit.getScore());
            //每次循环各取高低一个单位
            while (restCnt > 0) {
                if (hEntry == null && lEntry == null) {
                    break;
                }
                //寻找分高的玩家
                while (hEntry != null && hEntry.getValue().size() == 0) {
                    hEntry = matchpool.higherEntry(hEntry.getKey());
                    if (hEntry == null) {
                        break;
                    }
                }
                if (hEntry != null) {
                    if (Math.abs(hEntry.getKey() - mUnit.getScore()) > Max_Score_Diff) {
                        hEntry = null;
                    } else {
                        IUnit pickUnit = hEntry.getValue().poll();
                        if (pickUnit != null) {
                            results.add(pickUnit);
                            restCnt = restCnt - pickUnit.getSize();
                        }
                    }
                }
                //寻找分低的玩家
                while (lEntry != null && lEntry.getValue().size() == 0) {
                    lEntry = matchpool.lowerEntry(lEntry.getKey());
                    if (lEntry == null) {
                        break;
                    }

                }
                if (lEntry != null) {
                    if (Math.abs(lEntry.getKey() - mUnit.getScore()) > Max_Score_Diff) {
                        lEntry = null;
                    } else {
                        IUnit pickUnit = lEntry.getValue().poll();
                        if (pickUnit != null) {
                            results.add(pickUnit);
                            restCnt = restCnt - pickUnit.getSize();
                        }
                    }
                }
            }
        }
        Match match = new Match(gameType);
        List<IUnit> toReturn = results;
        //有足够的玩家进行二次挑选
        if (restCnt <= Buffer_Match) {
            Collections.sort(results, new Comparator<IUnit>() {
                //玩家选择顺序规则
                @Override
                public int compare(IUnit o1, IUnit o2) {
                    int absSize1 = Math.abs(mUnit.getSize() - o1.getSize());
                    int absSize2 = Math.abs(mUnit.getSize() - o2.getSize());
                    if (absSize1 == absSize2) {
                        return -1;
                    } else {
                        if (o1.getScore() < o2.getScore()) {
                            return -1;
                        } else if (o1.getScore() > o2.getScore()) {
                            return 1;
                        } else {
                            return 0;
                        }
                    }
                }
            });
            //开始进行二次挑选
            List<IUnit> results2 = new ArrayList<>(results);
            match.team1.add(mUnit);
            IUnit lastTeam1 = mUnit;
            IUnit lastTeam2 = null;
            while (true) {
                //挑选team2,需要满足unit的大小等于team1
                Iterator<IUnit> iter2 = results2.iterator();
                lastTeam2 = null;
                while (match.team2size() < gameType.pcnt && iter2.hasNext()) {
                    IUnit pickUnit = iter2.next();
                    if (pickUnit.getSize() == lastTeam1.getSize()) {
                        lastTeam2 = pickUnit;
                        match.team2.add(lastTeam2);
                        iter2.remove();
                        break;
                    }
                }
                //找不到team2,退出
                if (lastTeam2 == null) {
                    break;
                }
                //挑选team1
                Iterator<IUnit> iter1 = results2.iterator();
                lastTeam1 = null;
                while (match.team1size() < gameType.pcnt && iter1.hasNext()) {
                    IUnit pickUnit = iter1.next();
                    if (pickUnit.getSize() + match.team1size() <= gameType.pcnt) {
                        lastTeam1 = pickUnit;
                        match.team1.add(lastTeam1);
                        iter1.remove();
                        break;
                    }
                }
                //找不到team1,退出
                if (lastTeam1 == null) {
                    break;
                }
            }
            if (match.team1size() == gameType.pcnt && match.team2size() == gameType.pcnt) {
                toReturn = results2;
            } else {
                toReturn.add(mUnit); //需要把当前玩家也加进去
                match = null;
            }
        } else {
            toReturn.add(mUnit); //需要把当前玩家也加进去
            match = null;
        }
        //还原
        Iterator<IUnit> iReturn = toReturn.iterator();
        while (iReturn.hasNext()) {
            IUnit picked = iReturn.next();
            Queue<IUnit> queue = matchpool.get(picked.getScore());
            if (queue == null) {
                queue = new LinkedBlockingQueue<>();
                matchpool.put(picked.getScore(), queue);
            }
            queue.add(picked);
        }
        if (match == null) {
            return null;
        } else {
            //平衡team1和team2的分数
            Match matchret = new Match(match.gameType);
            for (int i = 0; i < match.team1.size(); i++) {
                boolean team1higher = matchret.scoreDiff() > 0;
                boolean team2higher = (match.team2.get(i).getScore() - match.team1.get(i).getScore()) > 0;
                if (team1higher ^ team2higher) {
                    matchret.team1.add(match.team2.get(i));
                    matchret.team2.add(match.team1.get(i));
                } else {
                    matchret.team1.add(match.team1.get(i));
                    matchret.team2.add(match.team2.get(i));
                }
            }
            return match;
        }
    }

    public synchronized Map<GameType, Map<Integer, List<IUnit>>> getMatchPools() {
        Map ret = new HashMap();
        for (Map.Entry<GameType, TreeMap<Integer, Queue<IUnit>>> mapEntry : this.matchPools.entrySet()) {
            HashMap<Integer, List<IUnit>> retEntry = new HashMap();
            for (Map.Entry<Integer, Queue<IUnit>> entry : mapEntry.getValue().entrySet()) {
                retEntry.put(entry.getKey(), new ArrayList<IUnit>(entry.getValue()));
            }
            ret.put(mapEntry.getKey(), retEntry);
        }

        return ret;
    }

    public synchronized Map<Integer, List<IUnit>> getMatchPool(Integer gameTypeKey) throws OpException {
        TreeMap<Integer, Queue<IUnit>> matchpool = this.matchPools.get(GameType.get(gameTypeKey));
        if (matchpool == null) {
            throw new OpException(OpResult.INVALID, "找不到该比赛类型" + gameTypeKey);
        }
        HashMap<Integer, List<IUnit>> ret = new HashMap();
        for (Map.Entry<Integer, Queue<IUnit>> entry : matchpool.entrySet()) {
            ret.put(entry.getKey(), new ArrayList<IUnit>(entry.getValue()));
        }
        return ret;
    }

    public synchronized void cleanMatchPool(Integer gameTypeKey) throws OpException {
        TreeMap<Integer, Queue<IUnit>> matchpool = this.matchPools.get(GameType.get(gameTypeKey));
        if (matchpool == null) {
            throw new OpException(OpResult.INVALID, "找不到该比赛类型" + gameTypeKey);
        }
        matchpool.clear();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        for (GameType gameType : GameType.getall().values()) {
            matchPools.put(gameType, new TreeMap<Integer, Queue<IUnit>>());
        }
    }

}

数据结构

IUnit

package com.fym.match.obj;

/**
 *
 * Created by fengy on 2016/5/25.
 */
public interface IUnit {
    int getScore();

    int getSize();

}

Person

package com.fym.match.obj;

/**
 *
 * Created by fengy on 2016/5/25.
 */
public class Person implements IUnit {

    /**
     * 玩家id
     */
    private int pid;

    /**
     * 分值
     */
    private int score;

    public Person(int pid, int score) {
        this.pid = pid;
        this.score = score;
    }

    @Override
    public int getScore() {
        return this.score;
    }

    @Override
    public int getSize() {
        return 1;
    }

}

Group

package com.fym.match.obj;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 *
 * Created by fengy on 2016/5/25.
 */
public class Group implements IUnit {
    /**
     * 分数
     */
    private int score;

    /**
     * 玩家id
     */
    private List<Person> persons;

    public Group(Collection<Person> persons) {
        this.persons = new ArrayList<>(persons);
        int sum = 0;
        for (Person person : this.persons) {
            sum += person.getScore();
        }
        this.score = sum / persons.size() + persons.size() * 10;
    }

    @Override
    public int getScore() {
        return this.score;
    }

    @Override
    public int getSize() {
        return this.persons.size();
    }

}

Match

package com.fym.match.obj;

import com.fym.game.enm.GameType;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * Created by fengy on 2016/5/25.
 */
public class Match {
    public GameType gameType;
    public List<IUnit> team1;
    public List<IUnit> team2;

    public Match(GameType gameType) {
        this.gameType = gameType;
        this.team1 = new ArrayList<>(gameType.pcnt);
        this.team2 = new ArrayList<>(gameType.pcnt);
    }

    public int team1size() {
        return this.teamsizeCore(this.team1);
    }

    public int team2size() {
        return this.teamsizeCore(this.team2);
    }

    private static int teamsizeCore(List<IUnit> team) {
        int size = 0;
        for (IUnit iUnit : team) {
            size = size + iUnit.getSize();
        }
        return size;
    }

    public int team1score() {
        return this.teamscoreCore(this.team1);
    }

    public int team2score() {
        return this.teamscoreCore(this.team2);
    }

    public int scoreDiff() {
        return (this.team1score() - this.team2score());
    }

    private static int teamscoreCore(List<IUnit> team) {
        int score = 0;
        for (IUnit iUnit : team) {
            score = score + iUnit.getSize();
        }
        return score;
    }

}
时间: 2024-10-27 12:37:54

天梯匹配系统 - 简单实现的相关文章

linux小白-基础篇-系统简单优化

作为一个刚刚接触Linux的小白,通过一段时间的学习,将我自己的部分笔记整理后拿出来,请求大家指点:因为是"0"基础学起很多地方了解的都不够深入,希望各位前辈能够指点一下:予人玫瑰,手留余香,谢谢! 因为条件有限环境是用VM搭建起来的,下文主要是根据"老男孩教育视频"中的内容整理出来的系统简单优化,希望各位前辈看过以后可以给出建议: 已学习进步为目的,不喜勿喷!谢谢! 系统简单优化    命令 一.关闭selinux                         

Ubuntu学习笔记-win7&Ubuntu双系统简单搭建系统指南

win7&Ubuntu双系统简单搭建系统指南 本文是自己老本子折腾Ubuntu的一些记录,主要是搭建了一个能够足够娱乐(不玩游戏)专注练习自己编程能力的内容.只是简单的写了关于系统的安装和一些配置环境的简单搭建.并没有深入探讨系统地各项内容.希望可以给香简单使用的同学参考. 一.准备工作 打开UltraISO ,依次点击"文件"--"打开"--选择Ubuntu14.04系统镜像文件,确认打开后就能在软件界面内看到整个镜像的全部文件信息. 接下来开始制作系统安

基于Struct的云和租房系统(简单房屋出租)

基于Struct的云和租房系统(简单房屋出租) 系统采用javaBean实现ORM对象关系映射,前台纯JSP实现,后台struct映射,适合刚学习J2EE的新手,代码思路清晰,注解详细,数据库用的是mysql5.1,服务器用的tomcat7,JDK版本1.7. 编程软件Eclispe J2EE版本.是典型MVC架构,并且前后台分离,亦可以当做房屋中介房屋出租房屋销售管理系统二手房交易管理系统.具体功能这里不再赘述,请下方看系统详细演示图,如果大家有什么疑问或者什么不懂得可以在下方给我留言,或者你

正则引擎入门&mdash;&mdash;正则文法匹配可以简单快捷(三)

整篇文章是对作者Russ Cox的文章Regular Expression Matching Can Be Simple And Fast的翻译,在我看来,该文章是入门正则引擎的较好的文章之一,读者在阅读之前,最好有一定的正则表达式的基础.翻译内容仅代表作者观点.侵删 该作者所有的文章的网址在此:https://swtch.com/~rsc/regexp/ 正文 正则表达式搜索算法 现在我们已经有了确定一个正则表达式是否匹配一个字符串的方法,将正则表达式转换为NFA之后以字符串为输入运行该NFA

帧同步坦克大战匹配系统

在介绍匹配系统之前,先说一下项目的整体结构. 项目主要有四个场景: start --> home --> choose --> game start_scene: 是游戏的开始场景,在此场景主要做了微信授权,获取用户的昵称,头像等信息.通过云函数获取用户的openid(不了解云函数的可以参考这篇文章:微信云开发使用教程).然后把用户的信息和用户的openid都放到全局对象Global里边,方便以后使用. home_scene: 授权成功之后,就跳转到此场景. 进入此场景之后,需要初始化M

linux 系统简单的优化

简单的优化,大概从以下几个方面来优化 1.ssh服务的优化 2.selinux/iptables 3.字符集调整 4.开机自启动服务优化 5.时间优化(定时任务) ssh服务的优化 A.修改默认的端口 修改方法. vim /etc/ssh/sshd_config(修改配置文件) 修改配置文件的13行把默认的22成其他的.注意改完后把注释去掉 B.禁止root用户远程登陆 修改配置文件的42行把yes改成no C.禁止无密码登陆 通过ssh登陆的时候要不要密码.当然要密码所以yes默认的也是yes

第二篇:基于K-近邻分类算法的约会对象智能匹配系统

前言 假如你想到某个在线约会网站寻找约会对象,那么你很可能将该约会网站的所有用户归为三类: 1. 不喜欢的 2. 有点魅力的 3. 很有魅力的 你如何决定某个用户属于上述的哪一类呢?想必你会分析用户的信息来得到结论,比如该用户 "每年获得的飞行常客里程数","玩网游所消耗的时间比","每年消耗的冰淇淋公升数". 使用机器学习的K-近邻算法,可以帮助你在获取到用户的这三个信息后(或者更多信息 方法同理),自动帮助你对该用户进行分类,多方便呀! 本文

Java实验项目二——小学生考试系统(简单四则运算)

Program:设计实现一个小学生数学考试系统,完成随机出题(简单的四则运算),学生答题,自动判分的功能. Description:代码如下: 1 /* 2 * Description:面向考试系统建立类TestSystem 3 * 4 * */ 5 6 package entity; 7 8 public class TestSystem { 9 10 private int num1; //声明两个操作数 11 private int num2; 12 private String oper

Esxi虚拟机安装Ros+Openwrt软路由双系统简单分享(踩到的坑,很大的坑)

近段时间ke学上网反应很慢,网上看到了 Ros+Openwr能够解决DHCP污染的这个问题,所以看看自己的3825U小主机刚可以满足要求,搞一下吧. 听说L大的openwrt软路由固件ke学上网很不错,所以找了一个大神编译好的.还有就是ROS系统可以做DNS缓存加快网址的解析.在论坛各种群里均可以找到. 以下是准备的东西. 1.openwrt软路由固件( 20200318openwrt-x86-64-combined-squashfs.img) (旁路由固件,网关192.168.119.251)