天梯匹配系统 - 简单实现

来源:互联网 发布:node.js 编译 编辑:程序博客网 时间:2024/05/17 16:54

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

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. */@Componentpublic 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;    }} 


0 0