1.修复匹配时可能会匹配到死去玩家的逻辑

2.修复匹配的随机性条件,改为完全随机
This commit is contained in:
2025-10-11 16:52:39 +08:00
parent 6aa8fb818a
commit 75c483103e
2 changed files with 149 additions and 227 deletions

View File

@@ -5,8 +5,8 @@ exports.createMatchmaker = createMatchmaker;
exports.displayPairings = displayPairings; exports.displayPairings = displayPairings;
class Matchmaker { class Matchmaker {
constructor(players) { constructor(players) {
this.roundHistory = []; // 记录每轮的配对历史
this.currentRound = 0; this.currentRound = 0;
this.restingHistory = new Map(); // 记录每个玩家的轮空次数
this.mPlayers = players; this.mPlayers = players;
} }
/** /**
@@ -14,7 +14,7 @@ class Matchmaker {
* @returns 配对结果数组 * @returns 配对结果数组
*/ */
generatePairings() { generatePairings() {
const playerIds = Array.from(this.mPlayers.keys()); const playerIds = Array.from(this.mPlayers.keys()); // 只使用当前存活的玩家
const playerCount = playerIds.length; const playerCount = playerIds.length;
if (playerCount === 0) { if (playerCount === 0) {
return []; return [];
@@ -24,21 +24,19 @@ class Matchmaker {
return [{ return [{
playerId: playerIds[0], playerId: playerIds[0],
opponentId: playerIds[0], opponentId: playerIds[0],
// socketID: this.mPlayers.get(playerIds[0])!.mSocketId,
isResting: true isResting: true
}]; }];
} }
const pairings = this.createOptimalPairings(playerIds); const pairings = this.createRandomPairings(playerIds);
this.roundHistory.push(pairings.map(p => `${p.playerId}-${p.opponentId}`));
this.currentRound++; this.currentRound++;
return pairings; return pairings;
} }
/** /**
* 创建最优配对 * 创建随机配对 - 使用 Fisher-Yates 洗牌算法
* @param playerIds 玩家ID数组 * @param playerIds 玩家ID数组
* @returns 配对结果 * @returns 配对结果
*/ */
createOptimalPairings(playerIds) { createRandomPairings(playerIds) {
const playerCount = playerIds.length; const playerCount = playerIds.length;
const isOddCount = playerCount % 2 === 1; const isOddCount = playerCount % 2 === 1;
// 如果是奇数,需要找一个玩家轮空 // 如果是奇数,需要找一个玩家轮空
@@ -48,31 +46,24 @@ class Matchmaker {
restingPlayer = this.selectRestingPlayer(playerIds); restingPlayer = this.selectRestingPlayer(playerIds);
activePlayers = playerIds.filter(id => id !== restingPlayer); activePlayers = playerIds.filter(id => id !== restingPlayer);
} }
// 对剩余玩家进行配对 // Fisher-Yates 洗牌算法 - O(n) 时间复杂度
const shuffled = this.shuffleArray(activePlayers);
const pairings = []; const pairings = [];
const usedPlayers = new Set(); // 按顺序两两配对 - O(n) 时间复杂度
// 使用轮转算法确保配对的多样性 for (let i = 0; i < shuffled.length; i += 2) {
const rotatedPlayers = this.rotatePlayerOrder(activePlayers); if (i + 1 < shuffled.length) {
for (let i = 0; i < rotatedPlayers.length; i += 2) { const player1 = shuffled[i];
if (i + 1 < rotatedPlayers.length) { const player2 = shuffled[i + 1];
const player1 = rotatedPlayers[i];
const player2 = rotatedPlayers[i + 1];
if (!usedPlayers.has(player1) && !usedPlayers.has(player2)) {
pairings.push({ pairings.push({
playerId: player1, playerId: player1,
opponentId: player2, opponentId: player2,
// socketID: this.mPlayers.get(player1)!.mSocketId,
isResting: false isResting: false
}); });
pairings.push({ pairings.push({
playerId: player2, playerId: player2,
opponentId: player1, opponentId: player1,
// socketID: this.mPlayers.get(player2)!.mSocketId,
isResting: false isResting: false
}); });
usedPlayers.add(player1);
usedPlayers.add(player2);
}
} }
} }
// 处理轮空玩家 // 处理轮空玩家
@@ -81,107 +72,76 @@ class Matchmaker {
pairings.push({ pairings.push({
playerId: restingPlayer, playerId: restingPlayer,
opponentId: ghostOpponent, opponentId: ghostOpponent,
// socketID: this.mPlayers.get(restingPlayer)!.mSocketId,
isResting: true isResting: true
}); });
} }
return pairings; return pairings;
} }
/**
* Fisher-Yates 洗牌算法 - 完全随机且高效
* @param array 要洗牌的数组
* @returns 洗牌后的新数组
*/
shuffleArray(array) {
const result = [...array];
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}
/** /**
* 选择轮空玩家(轮换制,确保每个玩家都有机会轮空) * 选择轮空玩家(轮换制,确保每个玩家都有机会轮空)
* @param playerIds 玩家ID数组 * @param playerIds 玩家ID数组(当前存活的玩家)
* @returns 轮空玩家ID * @returns 轮空玩家ID
*/ */
selectRestingPlayer(playerIds) { selectRestingPlayer(playerIds) {
const restingCounts = new Map(); // 初始化轮空次数记录
// 统计每个玩家的轮空次数 playerIds.forEach(id => {
playerIds.forEach(id => restingCounts.set(id, 0)); if (!this.restingHistory.has(id)) {
this.roundHistory.forEach(round => { this.restingHistory.set(id, 0);
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; let minRestingCount = Infinity;
playerIds.forEach(id => { playerIds.forEach(id => {
const count = restingCounts.get(id) || 0; const count = this.restingHistory.get(id) || 0;
if (count < minRestingCount) { if (count < minRestingCount) {
minRestingCount = count; minRestingCount = count;
} }
}); });
const candidatePlayers = playerIds.filter(id => { const candidatePlayers = playerIds.filter(id => {
const count = restingCounts.get(id) || 0; const count = this.restingHistory.get(id) || 0;
return count === minRestingCount; return count === minRestingCount;
}); });
// 在轮空次数最少的玩家中轮换选择 // 在轮空次数最少的玩家中随机选择
// 使用轮次数来确定选择哪个玩家,确保轮换 const selectedIndex = Math.floor(Math.random() * candidatePlayers.length);
const selectedIndex = this.currentRound % candidatePlayers.length; const selectedPlayer = candidatePlayers[selectedIndex];
return candidatePlayers[selectedIndex]; // 增加该玩家的轮空次数
this.restingHistory.set(selectedPlayer, (this.restingHistory.get(selectedPlayer) || 0) + 1);
return selectedPlayer;
} }
/** /**
* 为轮空玩家选择一个"幽灵对手"(用于显示和计算 * 为轮空玩家选择一个"幽灵对手"(用于显示)
* @param restingPlayer 轮空玩家ID * @param restingPlayer 轮空玩家ID
* @param allPlayers 所有玩家ID * @param allPlayers 所有当前存活的玩家ID
* @returns 幽灵对手ID * @returns 幽灵对手ID
*/ */
selectGhostOpponent(restingPlayer, allPlayers) { selectGhostOpponent(restingPlayer, allPlayers) {
// 选择与轮空玩家对战次数最少的玩家作为幽灵对手 // 只在当前存活的玩家中选择幽灵对手
const opponentCounts = new Map(); const alivePlayers = allPlayers.filter(id => id !== restingPlayer);
allPlayers.forEach(id => { if (alivePlayers.length === 0) {
if (id !== restingPlayer) { return restingPlayer; // 如果没有其他存活玩家,返回自己
opponentCounts.set(id, 0);
} }
}); // 随机选择一个幽灵对手
this.roundHistory.forEach(round => { const randomIndex = Math.floor(Math.random() * alivePlayers.length);
round.forEach(pairing => { return alivePlayers[randomIndex];
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() { resetHistory() {
this.roundHistory = []; this.restingHistory.clear();
this.currentRound = 0; this.currentRound = 0;
} }
/** /**
@@ -190,11 +150,20 @@ class Matchmaker {
*/ */
updatePlayers(newPlayers) { updatePlayers(newPlayers) {
this.mPlayers = newPlayers; this.mPlayers = newPlayers;
// 清理已死亡玩家的轮空历史记录
const alivePlayerIds = new Set(newPlayers.keys());
for (const playerId of this.restingHistory.keys()) {
if (!alivePlayerIds.has(playerId)) {
this.restingHistory.delete(playerId);
}
}
} }
deletePlayer(playerId) { deletePlayer(playerId) {
if (this.mPlayers.has(playerId)) { if (this.mPlayers.has(playerId)) {
this.mPlayers.delete(playerId); this.mPlayers.delete(playerId);
} }
// 清理该玩家的轮空历史
this.restingHistory.delete(playerId);
} }
} }
exports.Matchmaker = Matchmaker; exports.Matchmaker = Matchmaker;
@@ -209,11 +178,7 @@ function displayPairings(pairings) {
pairings.forEach(pairing => { pairings.forEach(pairing => {
const pairKey = [pairing.playerId, pairing.opponentId].sort().join('-'); const pairKey = [pairing.playerId, pairing.opponentId].sort().join('-');
if (!processedPairs.has(pairKey)) { if (!processedPairs.has(pairKey)) {
// if (pairing.isResting) {
// console.log(`${pairing.playerId} 轮空 (对手: ${pairing.opponentId})`);
// } else {
console.log(`${pairing.playerId} VS ${pairing.opponentId}`); console.log(`${pairing.playerId} VS ${pairing.opponentId}`);
// }
processedPairs.add(pairKey); processedPairs.add(pairKey);
} }
}); });

View File

@@ -3,14 +3,13 @@ import { Player } from "./Player";
interface PairingResult { interface PairingResult {
playerId: string; playerId: string;
opponentId: string; opponentId: string;
// socketID: string;
isResting: boolean; // 是否为轮空玩家 isResting: boolean; // 是否为轮空玩家
} }
class Matchmaker { class Matchmaker {
private mPlayers: Map<string, Player>; private mPlayers: Map<string, Player>;
private roundHistory: string[][] = []; // 记录每轮的配对历史
private currentRound: number = 0; private currentRound: number = 0;
private restingHistory: Map<string, number> = new Map(); // 记录每个玩家的轮空次数
constructor(players: Map<string, Player>) { constructor(players: Map<string, Player>) {
this.mPlayers = players; this.mPlayers = players;
@@ -21,7 +20,7 @@ interface PairingResult {
* @returns 配对结果数组 * @returns 配对结果数组
*/ */
public generatePairings(): PairingResult[] { public generatePairings(): PairingResult[] {
const playerIds = Array.from(this.mPlayers.keys()); const playerIds = Array.from(this.mPlayers.keys()); // 只使用当前存活的玩家
const playerCount = playerIds.length; const playerCount = playerIds.length;
if (playerCount === 0) { if (playerCount === 0) {
@@ -33,24 +32,22 @@ interface PairingResult {
return [{ return [{
playerId: playerIds[0], playerId: playerIds[0],
opponentId: playerIds[0], opponentId: playerIds[0],
// socketID: this.mPlayers.get(playerIds[0])!.mSocketId,
isResting: true isResting: true
}]; }];
} }
const pairings = this.createOptimalPairings(playerIds); const pairings = this.createRandomPairings(playerIds);
this.roundHistory.push(pairings.map(p => `${p.playerId}-${p.opponentId}`));
this.currentRound++; this.currentRound++;
return pairings; return pairings;
} }
/** /**
* 创建最优配对 * 创建随机配对 - 使用 Fisher-Yates 洗牌算法
* @param playerIds 玩家ID数组 * @param playerIds 玩家ID数组
* @returns 配对结果 * @returns 配对结果
*/ */
private createOptimalPairings(playerIds: string[]): PairingResult[] { private createRandomPairings(playerIds: string[]): PairingResult[] {
const playerCount = playerIds.length; const playerCount = playerIds.length;
const isOddCount = playerCount % 2 === 1; const isOddCount = playerCount % 2 === 1;
@@ -63,35 +60,27 @@ interface PairingResult {
activePlayers = playerIds.filter(id => id !== restingPlayer); activePlayers = playerIds.filter(id => id !== restingPlayer);
} }
// 对剩余玩家进行配对 // Fisher-Yates 洗牌算法 - O(n) 时间复杂度
const shuffled = this.shuffleArray(activePlayers);
const pairings: PairingResult[] = []; const pairings: PairingResult[] = [];
const usedPlayers = new Set<string>();
// 使用轮转算法确保配对的多样性 // 按顺序两两配对 - O(n) 时间复杂度
const rotatedPlayers = this.rotatePlayerOrder(activePlayers); for (let i = 0; i < shuffled.length; i += 2) {
if (i + 1 < shuffled.length) {
const player1 = shuffled[i];
const player2 = shuffled[i + 1];
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({ pairings.push({
playerId: player1, playerId: player1,
opponentId: player2, opponentId: player2,
// socketID: this.mPlayers.get(player1)!.mSocketId,
isResting: false isResting: false
}); });
pairings.push({ pairings.push({
playerId: player2, playerId: player2,
opponentId: player1, opponentId: player1,
// socketID: this.mPlayers.get(player2)!.mSocketId,
isResting: false isResting: false
}); });
usedPlayers.add(player1);
usedPlayers.add(player2);
}
} }
} }
@@ -101,7 +90,6 @@ interface PairingResult {
pairings.push({ pairings.push({
playerId: restingPlayer, playerId: restingPlayer,
opponentId: ghostOpponent, opponentId: ghostOpponent,
// socketID: this.mPlayers.get(restingPlayer)!.mSocketId,
isResting: true isResting: true
}); });
} }
@@ -109,116 +97,81 @@ interface PairingResult {
return pairings; return pairings;
} }
/**
* Fisher-Yates 洗牌算法 - 完全随机且高效
* @param array 要洗牌的数组
* @returns 洗牌后的新数组
*/
private shuffleArray<T>(array: T[]): T[] {
const result = [...array];
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}
/** /**
* 选择轮空玩家(轮换制,确保每个玩家都有机会轮空) * 选择轮空玩家(轮换制,确保每个玩家都有机会轮空)
* @param playerIds 玩家ID数组 * @param playerIds 玩家ID数组(当前存活的玩家)
* @returns 轮空玩家ID * @returns 轮空玩家ID
*/ */
private selectRestingPlayer(playerIds: string[]): string { private selectRestingPlayer(playerIds: string[]): string {
const restingCounts = new Map<string, number>(); // 初始化轮空次数记录
playerIds.forEach(id => {
// 统计每个玩家的轮空次数 if (!this.restingHistory.has(id)) {
playerIds.forEach(id => restingCounts.set(id, 0)); this.restingHistory.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; let minRestingCount = Infinity;
playerIds.forEach(id => { playerIds.forEach(id => {
const count = restingCounts.get(id) || 0; const count = this.restingHistory.get(id) || 0;
if (count < minRestingCount) { if (count < minRestingCount) {
minRestingCount = count; minRestingCount = count;
} }
}); });
const candidatePlayers = playerIds.filter(id => { const candidatePlayers = playerIds.filter(id => {
const count = restingCounts.get(id) || 0; const count = this.restingHistory.get(id) || 0;
return count === minRestingCount; return count === minRestingCount;
}); });
// 在轮空次数最少的玩家中轮换选择 // 在轮空次数最少的玩家中随机选择
// 使用轮次数来确定选择哪个玩家,确保轮换 const selectedIndex = Math.floor(Math.random() * candidatePlayers.length);
const selectedIndex = this.currentRound % candidatePlayers.length; const selectedPlayer = candidatePlayers[selectedIndex];
return candidatePlayers[selectedIndex];
// 增加该玩家的轮空次数
this.restingHistory.set(selectedPlayer, (this.restingHistory.get(selectedPlayer) || 0) + 1);
return selectedPlayer;
} }
/** /**
* 为轮空玩家选择一个"幽灵对手"(用于显示和计算 * 为轮空玩家选择一个"幽灵对手"(用于显示)
* @param restingPlayer 轮空玩家ID * @param restingPlayer 轮空玩家ID
* @param allPlayers 所有玩家ID * @param allPlayers 所有当前存活的玩家ID
* @returns 幽灵对手ID * @returns 幽灵对手ID
*/ */
private selectGhostOpponent(restingPlayer: string, allPlayers: string[]): string { private selectGhostOpponent(restingPlayer: string, allPlayers: string[]): string {
// 选择与轮空玩家对战次数最少的玩家作为幽灵对手 // 只在当前存活的玩家中选择幽灵对手
const opponentCounts = new Map<string, number>(); const alivePlayers = allPlayers.filter(id => id !== restingPlayer);
allPlayers.forEach(id => { if (alivePlayers.length === 0) {
if (id !== restingPlayer) { return 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;
} }
/** // 随机选择一个幽灵对手
* 根据轮次旋转玩家顺序,增加配对的随机性 const randomIndex = Math.floor(Math.random() * alivePlayers.length);
* @param playerIds 玩家ID数组 return alivePlayers[randomIndex];
* @returns 旋转后的玩家数组
*/
private rotatePlayerOrder(playerIds: string[]): string[] {
if (playerIds.length <= 2) {
return [...playerIds];
}
const rotateAmount = this.currentRound % playerIds.length;
return [
...playerIds.slice(rotateAmount),
...playerIds.slice(0, rotateAmount)
];
}
/**
* 获取配对历史
* @returns 历史配对记录
*/
public getPairingHistory(): string[][] {
return [...this.roundHistory];
} }
/** /**
* 重置配对历史(新游戏开始时调用) * 重置配对历史(新游戏开始时调用)
*/ */
public resetHistory(): void { public resetHistory(): void {
this.roundHistory = []; this.restingHistory.clear();
this.currentRound = 0; this.currentRound = 0;
} }
@@ -228,14 +181,22 @@ interface PairingResult {
*/ */
public updatePlayers(newPlayers: Map<string, Player>): void { public updatePlayers(newPlayers: Map<string, Player>): void {
this.mPlayers = newPlayers; this.mPlayers = newPlayers;
// 清理已死亡玩家的轮空历史记录
const alivePlayerIds = new Set(newPlayers.keys());
for (const playerId of this.restingHistory.keys()) {
if (!alivePlayerIds.has(playerId)) {
this.restingHistory.delete(playerId);
}
}
} }
public deletePlayer(playerId:string) public deletePlayer(playerId: string) {
{ if (this.mPlayers.has(playerId)) {
if(this.mPlayers.has(playerId))
{
this.mPlayers.delete(playerId); this.mPlayers.delete(playerId);
} }
// 清理该玩家的轮空历史
this.restingHistory.delete(playerId);
} }
} }
@@ -254,11 +215,7 @@ interface PairingResult {
const pairKey = [pairing.playerId, pairing.opponentId].sort().join('-'); const pairKey = [pairing.playerId, pairing.opponentId].sort().join('-');
if (!processedPairs.has(pairKey)) { if (!processedPairs.has(pairKey)) {
// if (pairing.isResting) {
// console.log(`${pairing.playerId} 轮空 (对手: ${pairing.opponentId})`);
// } else {
console.log(`${pairing.playerId} VS ${pairing.opponentId}`); console.log(`${pairing.playerId} VS ${pairing.opponentId}`);
// }
processedPairs.add(pairKey); processedPairs.add(pairKey);
} }
}); });