diff --git a/MatchMaker.js b/MatchMaker.js index d77f3bf..2256a42 100644 --- a/MatchMaker.js +++ b/MatchMaker.js @@ -5,8 +5,8 @@ exports.createMatchmaker = createMatchmaker; exports.displayPairings = displayPairings; class Matchmaker { constructor(players) { - this.roundHistory = []; // 记录每轮的配对历史 this.currentRound = 0; + this.restingHistory = new Map(); // 记录每个玩家的轮空次数 this.mPlayers = players; } /** @@ -14,7 +14,7 @@ class Matchmaker { * @returns 配对结果数组 */ generatePairings() { - const playerIds = Array.from(this.mPlayers.keys()); + const playerIds = Array.from(this.mPlayers.keys()); // 只使用当前存活的玩家 const playerCount = playerIds.length; if (playerCount === 0) { return []; @@ -24,21 +24,19 @@ class Matchmaker { 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}`)); + const pairings = this.createRandomPairings(playerIds); this.currentRound++; return pairings; } /** - * 创建最优配对 + * 创建随机配对 - 使用 Fisher-Yates 洗牌算法 * @param playerIds 玩家ID数组 * @returns 配对结果 */ - createOptimalPairings(playerIds) { + createRandomPairings(playerIds) { const playerCount = playerIds.length; const isOddCount = playerCount % 2 === 1; // 如果是奇数,需要找一个玩家轮空 @@ -48,31 +46,24 @@ class Matchmaker { restingPlayer = this.selectRestingPlayer(playerIds); activePlayers = playerIds.filter(id => id !== restingPlayer); } - // 对剩余玩家进行配对 + // Fisher-Yates 洗牌算法 - O(n) 时间复杂度 + const shuffled = this.shuffleArray(activePlayers); 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); - } + // 按顺序两两配对 - O(n) 时间复杂度 + for (let i = 0; i < shuffled.length; i += 2) { + if (i + 1 < shuffled.length) { + const player1 = shuffled[i]; + const player2 = shuffled[i + 1]; + pairings.push({ + playerId: player1, + opponentId: player2, + isResting: false + }); + pairings.push({ + playerId: player2, + opponentId: player1, + isResting: false + }); } } // 处理轮空玩家 @@ -81,107 +72,76 @@ class Matchmaker { pairings.push({ playerId: restingPlayer, opponentId: ghostOpponent, - // socketID: this.mPlayers.get(restingPlayer)!.mSocketId, isResting: true }); } 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 */ 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); - } - }); + // 初始化轮空次数记录 + playerIds.forEach(id => { + if (!this.restingHistory.has(id)) { + this.restingHistory.set(id, 0); + } }); - // 找到轮空次数最少的所有玩家 + // 找到轮空次数最少的玩家 let minRestingCount = Infinity; playerIds.forEach(id => { - const count = restingCounts.get(id) || 0; + const count = this.restingHistory.get(id) || 0; if (count < minRestingCount) { minRestingCount = count; } }); const candidatePlayers = playerIds.filter(id => { - const count = restingCounts.get(id) || 0; + const count = this.restingHistory.get(id) || 0; return count === minRestingCount; }); - // 在轮空次数最少的玩家中轮换选择 - // 使用轮次数来确定选择哪个玩家,确保轮换 - const selectedIndex = this.currentRound % candidatePlayers.length; - return candidatePlayers[selectedIndex]; + // 在轮空次数最少的玩家中随机选择 + const selectedIndex = Math.floor(Math.random() * candidatePlayers.length); + const selectedPlayer = candidatePlayers[selectedIndex]; + // 增加该玩家的轮空次数 + this.restingHistory.set(selectedPlayer, (this.restingHistory.get(selectedPlayer) || 0) + 1); + return selectedPlayer; } /** - * 为轮空玩家选择一个"幽灵对手"(用于显示和计算) + * 为轮空玩家选择一个"幽灵对手"(用于显示) * @param restingPlayer 轮空玩家ID - * @param allPlayers 所有玩家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 alivePlayers = allPlayers.filter(id => id !== restingPlayer); + if (alivePlayers.length === 0) { + return restingPlayer; // 如果没有其他存活玩家,返回自己 } - const rotateAmount = this.currentRound % playerIds.length; - return [ - ...playerIds.slice(rotateAmount), - ...playerIds.slice(0, rotateAmount) - ]; - } - /** - * 获取配对历史 - * @returns 历史配对记录 - */ - getPairingHistory() { - return [...this.roundHistory]; + // 随机选择一个幽灵对手 + const randomIndex = Math.floor(Math.random() * alivePlayers.length); + return alivePlayers[randomIndex]; } /** * 重置配对历史(新游戏开始时调用) */ resetHistory() { - this.roundHistory = []; + this.restingHistory.clear(); this.currentRound = 0; } /** @@ -190,11 +150,20 @@ class Matchmaker { */ updatePlayers(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) { if (this.mPlayers.has(playerId)) { this.mPlayers.delete(playerId); } + // 清理该玩家的轮空历史 + this.restingHistory.delete(playerId); } } exports.Matchmaker = Matchmaker; @@ -209,11 +178,7 @@ function displayPairings(pairings) { 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); } }); diff --git a/MatchMaker.ts b/MatchMaker.ts index 5733f44..93d65d6 100644 --- a/MatchMaker.ts +++ b/MatchMaker.ts @@ -3,14 +3,13 @@ import { Player } from "./Player"; interface PairingResult { playerId: string; opponentId: string; - // socketID: string; isResting: boolean; // 是否为轮空玩家 } class Matchmaker { private mPlayers: Map; - private roundHistory: string[][] = []; // 记录每轮的配对历史 private currentRound: number = 0; + private restingHistory: Map = new Map(); // 记录每个玩家的轮空次数 constructor(players: Map) { this.mPlayers = players; @@ -21,7 +20,7 @@ interface PairingResult { * @returns 配对结果数组 */ public generatePairings(): PairingResult[] { - const playerIds = Array.from(this.mPlayers.keys()); + const playerIds = Array.from(this.mPlayers.keys()); // 只使用当前存活的玩家 const playerCount = playerIds.length; if (playerCount === 0) { @@ -33,24 +32,22 @@ interface PairingResult { 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}`)); + const pairings = this.createRandomPairings(playerIds); this.currentRound++; return pairings; } /** - * 创建最优配对 + * 创建随机配对 - 使用 Fisher-Yates 洗牌算法 * @param playerIds 玩家ID数组 * @returns 配对结果 */ - private createOptimalPairings(playerIds: string[]): PairingResult[] { + private createRandomPairings(playerIds: string[]): PairingResult[] { const playerCount = playerIds.length; const isOddCount = playerCount % 2 === 1; @@ -63,35 +60,27 @@ interface PairingResult { activePlayers = playerIds.filter(id => id !== restingPlayer); } - // 对剩余玩家进行配对 - const pairings: PairingResult[] = []; - const usedPlayers = new Set(); - - // 使用轮转算法确保配对的多样性 - const rotatedPlayers = this.rotatePlayerOrder(activePlayers); + // Fisher-Yates 洗牌算法 - O(n) 时间复杂度 + const shuffled = this.shuffleArray(activePlayers); - for (let i = 0; i < rotatedPlayers.length; i += 2) { - if (i + 1 < rotatedPlayers.length) { - const player1 = rotatedPlayers[i]; - const player2 = rotatedPlayers[i + 1]; + const pairings: PairingResult[] = []; + + // 按顺序两两配对 - O(n) 时间复杂度 + for (let i = 0; i < shuffled.length; i += 2) { + if (i + 1 < shuffled.length) { + const player1 = shuffled[i]; + const player2 = shuffled[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); - } + pairings.push({ + playerId: player1, + opponentId: player2, + isResting: false + }); + pairings.push({ + playerId: player2, + opponentId: player1, + isResting: false + }); } } @@ -101,124 +90,88 @@ interface PairingResult { pairings.push({ playerId: restingPlayer, opponentId: ghostOpponent, - // socketID: this.mPlayers.get(restingPlayer)!.mSocketId, isResting: true }); } return pairings; } + + /** + * Fisher-Yates 洗牌算法 - 完全随机且高效 + * @param array 要洗牌的数组 + * @returns 洗牌后的新数组 + */ + private shuffleArray(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 */ private selectRestingPlayer(playerIds: string[]): string { - 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); - } - }); + // 初始化轮空次数记录 + playerIds.forEach(id => { + if (!this.restingHistory.has(id)) { + this.restingHistory.set(id, 0); + } }); - - // 找到轮空次数最少的所有玩家 + + // 找到轮空次数最少的玩家 let minRestingCount = Infinity; playerIds.forEach(id => { - const count = restingCounts.get(id) || 0; + const count = this.restingHistory.get(id) || 0; if (count < minRestingCount) { minRestingCount = count; } }); const candidatePlayers = playerIds.filter(id => { - const count = restingCounts.get(id) || 0; + const count = this.restingHistory.get(id) || 0; return count === minRestingCount; }); - // 在轮空次数最少的玩家中轮换选择 - // 使用轮次数来确定选择哪个玩家,确保轮换 - const selectedIndex = this.currentRound % candidatePlayers.length; - return candidatePlayers[selectedIndex]; + // 在轮空次数最少的玩家中随机选择 + const selectedIndex = Math.floor(Math.random() * candidatePlayers.length); + const selectedPlayer = candidatePlayers[selectedIndex]; + + // 增加该玩家的轮空次数 + this.restingHistory.set(selectedPlayer, (this.restingHistory.get(selectedPlayer) || 0) + 1); + + return selectedPlayer; } /** - * 为轮空玩家选择一个"幽灵对手"(用于显示和计算) + * 为轮空玩家选择一个"幽灵对手"(用于显示) * @param restingPlayer 轮空玩家ID - * @param allPlayers 所有玩家ID + * @param allPlayers 所有当前存活的玩家ID * @returns 幽灵对手ID */ private selectGhostOpponent(restingPlayer: string, allPlayers: string[]): string { - // 选择与轮空玩家对战次数最少的玩家作为幽灵对手 - const opponentCounts = new Map(); + // 只在当前存活的玩家中选择幽灵对手 + const alivePlayers = allPlayers.filter(id => id !== restingPlayer); - 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 旋转后的玩家数组 - */ - private rotatePlayerOrder(playerIds: string[]): string[] { - if (playerIds.length <= 2) { - return [...playerIds]; + if (alivePlayers.length === 0) { + return restingPlayer; // 如果没有其他存活玩家,返回自己 } - - const rotateAmount = this.currentRound % playerIds.length; - return [ - ...playerIds.slice(rotateAmount), - ...playerIds.slice(0, rotateAmount) - ]; - } - - /** - * 获取配对历史 - * @returns 历史配对记录 - */ - public getPairingHistory(): string[][] { - return [...this.roundHistory]; + + // 随机选择一个幽灵对手 + const randomIndex = Math.floor(Math.random() * alivePlayers.length); + return alivePlayers[randomIndex]; } /** * 重置配对历史(新游戏开始时调用) */ public resetHistory(): void { - this.roundHistory = []; + this.restingHistory.clear(); this.currentRound = 0; } @@ -228,14 +181,22 @@ interface PairingResult { */ public updatePlayers(newPlayers: Map): void { 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) - { - if(this.mPlayers.has(playerId)) - { + public deletePlayer(playerId: string) { + if (this.mPlayers.has(playerId)) { this.mPlayers.delete(playerId); } + // 清理该玩家的轮空历史 + this.restingHistory.delete(playerId); } } @@ -254,11 +215,7 @@ interface PairingResult { 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}`); - // } + console.log(`${pairing.playerId} VS ${pairing.opponentId}`); processedPairs.add(pairKey); } });