PHP怎么实现游戏玩家匹配,多人pvp玩法匹配算法的简单实现
在游戏中经常有多人参与的对战玩法,挺高玩家的互动乐趣,如data2,lol,守望先锋等竞技型游戏所有的战斗都是典型的多人匹配对战模式,如何保证对战的双方的平衡性,和公平性,就很重要。如lol等强竞技型的游戏采用的是ELO算法来实现的,比较复杂(有兴趣可以可以了解下ELO算法)。但对于手游特别是rpg类型的手游在公平性要求不是很高的情况下,简化了一些指标从而保证快速的开启一场战斗。
为保证一定的公平性,以及如何去匹配出一组对战阵营,我们给每个对战的玩家赋予一个积分的属性。以此来作为匹配的依据。当然为进一步增加公正性,可以加入一些其他的衡量属性,如战力,ip地址(防止组队开黑)等等。每场战斗基于战斗结果如果该玩家是获胜方,增加其积分值,反之减少(为避免出现负积分情况,失败了则增加少量积分值比较合适些)。
下面是主要代码的实现:
public class MatchForBattleTask {
private Set alreadyMatchTeams = new HashSet<>();
public void process() {
if (TestMatch.matchTeamInfos.isEmpty())
return;
int totalMatchRoleNum = 0;
for (Map.Entry entry : TestMatch.matchTeamInfos.entrySet()) {
totalMatchRoleNum += entry.getValue().roleinfos.size();
}
System.err.println("当前正在匹配中的玩家:"+totalMatchRoleNum);
List matchResults = new ArrayList<>();//匹配成功的玩家
Map> matchingCalssifyTeam = classifyTeams(TestMatch.matchTeamInfos);
List matchingGroupResult = getCampMatchGroupResult(matchingCalssifyTeam);
System.err.println("一共整合出:"+matchingGroupResult.size() +"个匹配组");
if (matchingGroupResult.isEmpty())
return;
int size = matchingGroupResult.size();
Set alreadyOccupied = new HashSet<>();
for (int i = 0; i < size; ++i) {
if (alreadyOccupied.contains(i))
continue;
MatchGroupResult tempGroup = matchingGroupResult.get(i);
int minVal = Integer.MAX_VALUE;
MatchResult matchResult = null;
int saveIndexI = -1;
int saveIndexJ = -1;
//这里可以优化下,遍历次数太多了点
for (int j = 0; j < size; ++j) {
if (i == j || alreadyOccupied.contains(j))
continue;
int subVal = Math.abs(tempGroup.averageScore - matchingGroupResult.get(j).averageScore);
//设置个积分差上限,超过这个上限就慢慢等着
if (subVal > 400)
continue;
if (subVal < minVal) {
matchResult = new MatchResult();
matchResult.blueGroup = matchingGroupResult.get(i);
matchResult.reGroup = matchingGroupResult.get(j);
minVal = subVal;
saveIndexI = i;
saveIndexJ = j;
}
}
if (null != matchResult && saveIndexI != -1 && saveIndexJ != -1) {
matchResults.add(matchResult);
alreadyOccupied.add(saveIndexI);
alreadyOccupied.add(saveIndexJ);
}
}
for (MatchResult matchResult : matchResults) {
for (long removeTeamId : matchResult.reGroup.teams.keySet())
TestMatch.matchTeamInfos.remove(removeTeamId);
for (long removeTeamId : matchResult.blueGroup.teams.keySet())
TestMatch.matchTeamInfos.remove(removeTeamId);
//TODO 通知创建战斗场景
}
System.err.println("共创建了:"+matchResults.size()+"个战场");
}
/**
* 整合所有的正在匹配的队伍数据,将其按照队伍人数分类,将5个人,4个人.....等等分类存储起来key:为队伍人数,value:队伍信息
* @param teamMap
* @return Map>
*/
private Map> classifyTeams(Map teamMap){
Map> classifyResult = new HashMap<>();
Set invalidateTeam = new HashSet();
for (Map.Entry entry : teamMap.entrySet()) {
MatchingTeamInfo teamInfo = entry.getValue();
long teamId = entry.getKey();
int memNum = teamInfo.roleinfos.size();
if (memNum <= 0) {
invalidateTeam.add(teamId);
continue;
}
MatchTeam mt = new MatchTeam();
mt.serverid = teamInfo.serverId;
mt.teamid = teamInfo.teamid;
int totalScore = 0;
for (MatchingRoleInfo roleInfo : teamInfo.roleinfos) {
totalScore += roleInfo.score;
mt.members.put(roleInfo.roleId, new BattleRoleInfo(roleInfo.roleId, roleInfo.score, roleInfo.name));
}
mt.totalScore = totalScore;
mt.averageScore = totalScore / memNum;
List resultList = classifyResult.get(memNum);
if (null == resultList) {
resultList = new ArrayList<>();
classifyResult.put(memNum, resultList);
}
resultList.add(mt);
}
//删除无效的队伍
for (long rmTeam : invalidateTeam)
teamMap.remove(rmTeam);
return classifyResult;
}
private void checkAddMatchGroupResult(List allTypeMatchResult, MatchGroupResult result) {
if (null == result)
return;
allTypeMatchResult.add(result);
}
/**
* 为当前队伍,填充数据 (如当前队伍中有两人,此时需要再从队伍人数分组中找3个填满,这个3个又可以进一步分为,找 3个一个人的队伍,或者找1个一个的队伍,一个两个人的队伍)
* @param mTeam 当期队伍
* @param campClassifyTeams 所有的分组队伍数据
* @param groupTypes 需要填充的数据
* @return
*/
private MatchGroupResult matchMostEqualGroup(MatchTeam mTeam, Map> campClassifyTeams, List groupTypes) {
final int myAverageScore = mTeam.averageScore;//当前队伍的平均分
Set cacheUsedTeam = new HashSet<>();//缓存已经分配过的队伍
MatchGroupResult result = new MatchGroupResult();
int totalScore = 0;
int totalMemNum = 0;
if (groupTypes.isEmpty()) {
cacheUsedTeam.add(mTeam.teamid);
result.teams.put(mTeam.teamid, mTeam);
totalScore += mTeam.totalScore;
totalMemNum += mTeam.members.size();
} else {
for (int groupType : groupTypes) {//根据group的信息,去找满足条件的数据
List checkFromList = campClassifyTeams.get(groupType);
if (null == checkFromList || checkFromList.isEmpty())
return null;
int minVal = Integer.MAX_VALUE;
MatchTeam currSelectTeam = null;
for (MatchTeam checkTeam : checkFromList) {
if (mTeam.teamid == checkTeam.teamid)
continue;
if (cacheUsedTeam.contains(checkTeam.teamid) || alreadyMatchTeams.contains(checkTeam.teamid))
continue;
//从满足条件的队伍组中找到与当前队伍积分最相近的队伍,此处可以设置个最大积分差的限制,挺高公平性
int sub = Math.abs(myAverageScore - checkTeam.averageScore);
if (sub < minVal) {
minVal = sub;
currSelectTeam = checkTeam;
}
}
if (null != currSelectTeam) {
cacheUsedTeam.add(currSelectTeam.teamid);
result.teams.put(currSelectTeam.teamid, currSelectTeam);
totalScore += currSelectTeam.totalScore;
totalMemNum += currSelectTeam.members.size();
} else
return null;
}
}
if (cacheUsedTeam.isEmpty() || totalMemNum == 0)
return null;
result.averageScore = totalScore / totalMemNum;
result.totalMemNum = totalMemNum;
result.totalScore = totalScore;
return result;
}
/**
* 匹配分配组情况
* 当队伍中有5个人时,直接放到类型为5的分组中
* 当队伍中有4个人时,组合情况就是 {4,1}
* 当队伍中有3个人时,组合情况就是{3,2},{3,1,1}
* 当队伍中有2个人时,组合情况就是{2,2,1},{2,1,1,1}
* 当队伍中有1个人时,组合情况及时{1,1,1,1,1}
* @param mTeam
* @param classifyTeams
* @return
*/
private MatchGroupResult matchBestGroup(MatchTeam mTeam, Map> classifyTeams) {
List allTypeMatchResult = new ArrayList<>();
switch (mTeam.members.size()) {
case 5:
checkAddMatchGroupResult(allTypeMatchResult, matchMostEqualGroup(mTeam, classifyTeams, Arrays.asList()));
break;
case 4:
checkAddMatchGroupResult(allTypeMatchResult, matchMostEqualGroup(mTeam, classifyTeams, Arrays.asList(1)));
break;
case 3:
checkAddMatchGroupResult(allTypeMatchResult, matchMostEqualGroup(mTeam, classifyTeams, Arrays.asList(2)));
checkAddMatchGroupResult(allTypeMatchResult, matchMostEqualGroup(mTeam, classifyTeams, Arrays.asList(1, 1)));
break;
case 2:
checkAddMatchGroupResult(allTypeMatchResult, matchMostEqualGroup(mTeam, classifyTeams, Arrays.asList(2, 1)));
checkAddMatchGroupResult(allTypeMatchResult, matchMostEqualGroup(mTeam, classifyTeams, Arrays.asList(2, 1, 1)));
break;
case 1:
checkAddMatchGroupResult(allTypeMatchResult, matchMostEqualGroup(mTeam, classifyTeams, Arrays.asList(1, 1, 1, 1)));
break;
default:
break;
}
MatchGroupResult finalResult = null;
final int mAverageScore = mTeam.averageScore;//当前队伍的平均积分
int minVal = Integer.MAX_VALUE;
//进一步再从组合情况中找到积分最相近的一个,然后把自己塞进去
for (MatchGroupResult result : allTypeMatchResult) {
int sub = Math.abs(mAverageScore - result.averageScore);
if (sub < minVal) {
minVal = sub;
finalResult = result;
}
}
if (null == finalResult)
return null;
finalResult.totalScore += mTeam.totalScore;
finalResult.totalMemNum += mTeam.members.size();
finalResult.teams.put(mTeam.teamid, mTeam);
return finalResult;
}
/**
* 将分类整合后的队伍进一步的去整合,将team中不足5人的队伍补满。
* @param campTeams
* @return
*/
private List getCampMatchGroupResult(Map> campTeams) {
List campMatchResult = new ArrayList<>();
//优先给人数多的队伍分配
for (int i = 5; i > 0; --i) {
List teams = campTeams.get(i);
if (null == teams || teams.isEmpty())
continue;
for (MatchTeam mt : teams) {
if (alreadyMatchTeams.contains(mt.teamid))
continue;
MatchGroupResult matchResult = matchBestGroup(mt, campTeams);
if (null == matchResult)
continue;
campMatchResult.add(matchResult);
alreadyMatchTeams.addAll(matchResult.teams.keySet());
}
}
return campMatchResult;
}
}
以及测试代码:
public class TestMatch {
//存储所有的匹配玩家数据,可以存储在数据库中,同样也可以用concurrentHashMap来存储(真实环境一定是多个线程操作该数据的)
public static Map matchTeamInfos = new HashMap<>();
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
long now = System.currentTimeMillis();
addTestData();
new MatchForBattleTask().process();
long end = System.currentTimeMillis();
System.err.println("耗时:"+(end - now)+"毫秒!");
}
};
//每隔5秒钟执行一次
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
service.scheduleAtFixedRate(runnable, 5, 5, TimeUnit.SECONDS);
}
/**
* 测试代码生成测试数据
*/
private static void addTestData() {
long baseId = System.currentTimeMillis()/1000;
int createTeamNum = getRandomBetween(5000, 7000);
int index = 0;
for (int i = 0; i < createTeamNum; ++i) {
MatchingTeamInfo maInfo = new MatchingTeamInfo();
maInfo.serverId = i;
maInfo.teamid = baseId + index;
TestMatch.matchTeamInfos.put(maInfo.teamid, maInfo);
int teamNum = getRandomBetween(1, 5);
for (int j = 0; j < teamNum; ++j) {
MatchingRoleInfo matchrole = new MatchingRoleInfo();
long temp = createTeamNum;
long roleId = (temp << 32) + i + j;
matchrole.roleId = roleId;
matchrole.score = getRandomBetween(0, 3000);
maInfo.roleinfos.add(matchrole);
}
index++;
}
}
public static int getRandomBetween(final int start, final int end) {
Random random = new Random();
return end > start ? random.nextInt(end - start + 1) + start : random.nextInt(start - end + 1) + end;
}
}
运行结果:
当前正在匹配中的玩家:20837
一共整合出:4155个匹配组
共创建了:2077个战场
耗时:614毫秒!
当前正在匹配中的玩家:16310
一共整合出:3251个匹配组
共创建了:1625个战场
耗时:836毫秒!
PHP怎么实现游戏玩家匹配,多人pvp玩法匹配算法的简单实现相关推荐
- c语言派对游戏,适合5,6人聚会玩的游戏
适合5,6人聚会玩的游戏 导语:5,6人聚会时玩什么游戏好呢?下面就由jy135小编给大家带来一些适合5,6人聚会玩的游戏,希望大家喜欢. 适合5,6人聚会玩的游戏 适合5,6人聚会玩的游戏--喝水传 ...
- 让虞书欣、李诞拍到停不下来!AR+AI双引擎的互动小游戏,如何打开IP新玩法?...
有眼尖的朋友已经发现,近期爱奇艺的热播剧们有了新"看"法:在日常追剧的间隙,结合了IP要素的趣味视频互动特效小游戏玩法吸引了百万用户参与互动的热潮,更激发了The9全员.李诞等娱乐 ...
- 小程序源码:游戏助手微信小程序源码王者荣耀战力查询,游戏扫码登录器-多玩法安装简单
这是一款游戏助手小程序源码,UI方面的话还是挺简单,小编个人看着挺舒服的 小程序内支持多种热门游戏扫码登录,也就是说是一个游戏登录器 比如英雄联盟,王者荣耀,和平精英等等热门游戏都支持 另外还有王者战 ...
- 又一款新品挺进美国iOS游戏畅销榜:除了做玩法混合,三消还能怎样赢得玩家?
白鲸出海在 9 月初曾报道,土耳其休闲游戏公司 Dream Games 宣布完成 5000 万美元融资,2 天之前,该公司的首款手游「Royal Match」上线.笔者注意到,到了 9 月底,这家公司 ...
- fla 走迷宫游戏 源码_迷宫新玩法,果断一试
迷宫, 真的是谜一样的存在, 大到几十岁的成年人, 小到三岁小儿, 都对其没有抵抗力. 而迷宫君也是真给力, 除了能给人带来愉悦感与成就感, 还能同时锻炼专注力.空间感.思维力.视觉追踪等, 是儿童感 ...
- 游戏商店系统定时限购玩法设计是否合理
今天在重写一个商店系统的服务端,并且对整套商店系统进行了一个结构性优化,新加了一个叫按时间限购的功能.这个功能的存在是否合理呢? 玩家操作游戏产生价值,如获得物品,价值货币,通过物品或者价值货币进行消 ...
- 微信小游戏的前端攻城狮玩法
转自自己在开源中国上的博客:https://my.oschina.net/u/7247... 前言 公司群里经常有人会发一些微信小游戏,每次下面都会跟好多晒分截图.比如这个<看你有多色>的 ...
- html5 营销小游戏,HTML5 营销:三种玩法+案例
近段时间小编发现,HTML5似乎满足了广告主对移动营销的大部分需求,从形式到功用.到传播,有人观望,更有人已抢了鲜,在2014年HTML5营销案例也层出不穷.大到可口可乐.维多利亚的秘密这种敢于尝鲜的 ...
- 无限幻斗那个服务器人多,《无限幻斗》旅团出新玩法 魔物狩猎探秘惊喜
网易首款跨次元实时共斗手游<无限幻斗>,近日推出了全新的旅团玩法,旅团成员在完成异界探险后,将有机会触发开启全新"魔物狩猎"副本.挑战猎魔Boss,将能获得丰厚的挑战奖 ...
最新文章
- docker删除所有容器_如何在Linux上创建,列出和删除Docker容器
- 如何用记事本写java_怎样简单的运用记事本写java程序
- 计算机视觉:目标检测的发展历程与基础概念
- 做计算机的小卫士教案,小卫士在行动小班教案
- EasyUI,二级页面内容的操作
- python datetime
- C++利用栈实现计算器
- 阿里云加入开放媒体联盟AOM 科技巨头联合推广高清视频新标准
- 安装MySQL+Navicat(提供安装包下载地址)
- python绘图之Times New Roman字体以及Helvetica字体
- 【线上直播ING】2016互联网金融应用发展半年报
- 华为复制加密门禁卡_将多种累赘门禁卡归一合并的最佳选择
- iOS 开发的一些奇巧淫技
- Latex 跨页图片跑到最后一页问题
- LabVIEW崩溃后所产生的错误日志文件的位置
- Emgucv不完整图像分割试验(十八)——Emgucv或opencv连接海康/萤石网络4G摄像头
- (十八)用JAVA编写MP3解码器——迷你播放器
- 数字签名标准(DSS)
- 防红直连php,全新网址缩短防封 QQ/微信防红 短网址生成系统PHP源码
- t6登录显示连接不到服务器,t6客户端登陆不到服务器