First update.
This commit is contained in:
220
MatchMaker.js
Normal file
220
MatchMaker.js
Normal file
@@ -0,0 +1,220 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Matchmaker = void 0;
|
||||
exports.createMatchmaker = createMatchmaker;
|
||||
exports.displayPairings = displayPairings;
|
||||
class Matchmaker {
|
||||
constructor(players) {
|
||||
this.roundHistory = []; // 记录每轮的配对历史
|
||||
this.currentRound = 0;
|
||||
this.mPlayers = players;
|
||||
}
|
||||
/**
|
||||
* 为当前轮次生成配对
|
||||
* @returns 配对结果数组
|
||||
*/
|
||||
generatePairings() {
|
||||
const playerIds = Array.from(this.mPlayers.keys());
|
||||
const playerCount = playerIds.length;
|
||||
if (playerCount === 0) {
|
||||
return [];
|
||||
}
|
||||
if (playerCount === 1) {
|
||||
// 只有一个玩家时,与自己配对(轮空)
|
||||
return [{
|
||||
playerId: playerIds[0],
|
||||
opponentId: playerIds[0],
|
||||
// socketID: this.mPlayers.get(playerIds[0])!.mSocketId,
|
||||
isResting: true
|
||||
}];
|
||||
}
|
||||
const pairings = this.createOptimalPairings(playerIds);
|
||||
this.roundHistory.push(pairings.map(p => `${p.playerId}-${p.opponentId}`));
|
||||
this.currentRound++;
|
||||
return pairings;
|
||||
}
|
||||
/**
|
||||
* 创建最优配对
|
||||
* @param playerIds 玩家ID数组
|
||||
* @returns 配对结果
|
||||
*/
|
||||
createOptimalPairings(playerIds) {
|
||||
const playerCount = playerIds.length;
|
||||
const isOddCount = playerCount % 2 === 1;
|
||||
// 如果是奇数,需要找一个玩家轮空
|
||||
let restingPlayer = null;
|
||||
let activePlayers = [...playerIds];
|
||||
if (isOddCount) {
|
||||
restingPlayer = this.selectRestingPlayer(playerIds);
|
||||
activePlayers = playerIds.filter(id => id !== restingPlayer);
|
||||
}
|
||||
// 对剩余玩家进行配对
|
||||
const pairings = [];
|
||||
const usedPlayers = new Set();
|
||||
// 使用轮转算法确保配对的多样性
|
||||
const rotatedPlayers = this.rotatePlayerOrder(activePlayers);
|
||||
for (let i = 0; i < rotatedPlayers.length; i += 2) {
|
||||
if (i + 1 < rotatedPlayers.length) {
|
||||
const player1 = rotatedPlayers[i];
|
||||
const player2 = rotatedPlayers[i + 1];
|
||||
if (!usedPlayers.has(player1) && !usedPlayers.has(player2)) {
|
||||
pairings.push({
|
||||
playerId: player1,
|
||||
opponentId: player2,
|
||||
// socketID: this.mPlayers.get(player1)!.mSocketId,
|
||||
isResting: false
|
||||
});
|
||||
pairings.push({
|
||||
playerId: player2,
|
||||
opponentId: player1,
|
||||
// socketID: this.mPlayers.get(player2)!.mSocketId,
|
||||
isResting: false
|
||||
});
|
||||
usedPlayers.add(player1);
|
||||
usedPlayers.add(player2);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 处理轮空玩家
|
||||
if (restingPlayer) {
|
||||
const ghostOpponent = this.selectGhostOpponent(restingPlayer, playerIds);
|
||||
pairings.push({
|
||||
playerId: restingPlayer,
|
||||
opponentId: ghostOpponent,
|
||||
// socketID: this.mPlayers.get(restingPlayer)!.mSocketId,
|
||||
isResting: true
|
||||
});
|
||||
}
|
||||
return pairings;
|
||||
}
|
||||
/**
|
||||
* 选择轮空玩家(轮换制,确保每个玩家都有机会轮空)
|
||||
* @param playerIds 玩家ID数组
|
||||
* @returns 轮空玩家ID
|
||||
*/
|
||||
selectRestingPlayer(playerIds) {
|
||||
const restingCounts = new Map();
|
||||
// 统计每个玩家的轮空次数
|
||||
playerIds.forEach(id => restingCounts.set(id, 0));
|
||||
this.roundHistory.forEach(round => {
|
||||
round.forEach(pairing => {
|
||||
const [playerId, opponentId] = pairing.split('-');
|
||||
if (playerId === opponentId) {
|
||||
const count = restingCounts.get(playerId) || 0;
|
||||
restingCounts.set(playerId, count + 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
// 找到轮空次数最少的所有玩家
|
||||
let minRestingCount = Infinity;
|
||||
playerIds.forEach(id => {
|
||||
const count = restingCounts.get(id) || 0;
|
||||
if (count < minRestingCount) {
|
||||
minRestingCount = count;
|
||||
}
|
||||
});
|
||||
const candidatePlayers = playerIds.filter(id => {
|
||||
const count = restingCounts.get(id) || 0;
|
||||
return count === minRestingCount;
|
||||
});
|
||||
// 在轮空次数最少的玩家中轮换选择
|
||||
// 使用轮次数来确定选择哪个玩家,确保轮换
|
||||
const selectedIndex = this.currentRound % candidatePlayers.length;
|
||||
return candidatePlayers[selectedIndex];
|
||||
}
|
||||
/**
|
||||
* 为轮空玩家选择一个"幽灵对手"(用于显示和计算)
|
||||
* @param restingPlayer 轮空玩家ID
|
||||
* @param allPlayers 所有玩家ID
|
||||
* @returns 幽灵对手ID
|
||||
*/
|
||||
selectGhostOpponent(restingPlayer, allPlayers) {
|
||||
// 选择与轮空玩家对战次数最少的玩家作为幽灵对手
|
||||
const opponentCounts = new Map();
|
||||
allPlayers.forEach(id => {
|
||||
if (id !== restingPlayer) {
|
||||
opponentCounts.set(id, 0);
|
||||
}
|
||||
});
|
||||
this.roundHistory.forEach(round => {
|
||||
round.forEach(pairing => {
|
||||
const [playerId, opponentId] = pairing.split('-');
|
||||
if (playerId === restingPlayer && opponentId !== restingPlayer) {
|
||||
const count = opponentCounts.get(opponentId) || 0;
|
||||
opponentCounts.set(opponentId, count + 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
let minCount = Infinity;
|
||||
let ghostOpponent = allPlayers.find(id => id !== restingPlayer) || restingPlayer;
|
||||
opponentCounts.forEach((count, playerId) => {
|
||||
if (count < minCount) {
|
||||
minCount = count;
|
||||
ghostOpponent = playerId;
|
||||
}
|
||||
});
|
||||
return ghostOpponent;
|
||||
}
|
||||
/**
|
||||
* 根据轮次旋转玩家顺序,增加配对的随机性
|
||||
* @param playerIds 玩家ID数组
|
||||
* @returns 旋转后的玩家数组
|
||||
*/
|
||||
rotatePlayerOrder(playerIds) {
|
||||
if (playerIds.length <= 2) {
|
||||
return [...playerIds];
|
||||
}
|
||||
const rotateAmount = this.currentRound % playerIds.length;
|
||||
return [
|
||||
...playerIds.slice(rotateAmount),
|
||||
...playerIds.slice(0, rotateAmount)
|
||||
];
|
||||
}
|
||||
/**
|
||||
* 获取配对历史
|
||||
* @returns 历史配对记录
|
||||
*/
|
||||
getPairingHistory() {
|
||||
return [...this.roundHistory];
|
||||
}
|
||||
/**
|
||||
* 重置配对历史(新游戏开始时调用)
|
||||
*/
|
||||
resetHistory() {
|
||||
this.roundHistory = [];
|
||||
this.currentRound = 0;
|
||||
}
|
||||
/**
|
||||
* 更新玩家列表(当有玩家加入或退出时调用)
|
||||
* @param newPlayers 新的玩家Map
|
||||
*/
|
||||
updatePlayers(newPlayers) {
|
||||
this.mPlayers = newPlayers;
|
||||
}
|
||||
deletePlayer(playerId) {
|
||||
if (this.mPlayers.has(playerId)) {
|
||||
this.mPlayers.delete(playerId);
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.Matchmaker = Matchmaker;
|
||||
// 使用示例和辅助函数
|
||||
function createMatchmaker(mPlayers) {
|
||||
return new Matchmaker(mPlayers);
|
||||
}
|
||||
// 显示配对结果的辅助函数
|
||||
function displayPairings(pairings) {
|
||||
console.log(`=== 第${pairings.length > 0 ? '当前' : '0'}轮配对结果 ===`);
|
||||
const processedPairs = new Set();
|
||||
pairings.forEach(pairing => {
|
||||
const pairKey = [pairing.playerId, pairing.opponentId].sort().join('-');
|
||||
if (!processedPairs.has(pairKey)) {
|
||||
// if (pairing.isResting) {
|
||||
// console.log(`${pairing.playerId} 轮空 (对手: ${pairing.opponentId})`);
|
||||
// } else {
|
||||
console.log(`${pairing.playerId} VS ${pairing.opponentId}`);
|
||||
// }
|
||||
processedPairs.add(pairKey);
|
||||
}
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user