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; constructor(players: Map) { this.mPlayers = players; } /** * 为当前轮次生成配对 * @returns 配对结果数组 */ public generatePairings(): PairingResult[] { 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 配对结果 */ private createOptimalPairings(playerIds: string[]): PairingResult[] { const playerCount = playerIds.length; const isOddCount = playerCount % 2 === 1; // 如果是奇数,需要找一个玩家轮空 let restingPlayer: string | null = null; let activePlayers = [...playerIds]; if (isOddCount) { restingPlayer = this.selectRestingPlayer(playerIds); activePlayers = playerIds.filter(id => id !== restingPlayer); } // 对剩余玩家进行配对 const pairings: PairingResult[] = []; 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 */ 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); } }); }); // 找到轮空次数最少的所有玩家 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 */ private selectGhostOpponent(restingPlayer: string, allPlayers: string[]): string { // 选择与轮空玩家对战次数最少的玩家作为幽灵对手 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 旋转后的玩家数组 */ 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 { this.roundHistory = []; this.currentRound = 0; } /** * 更新玩家列表(当有玩家加入或退出时调用) * @param newPlayers 新的玩家Map */ public updatePlayers(newPlayers: Map): void { this.mPlayers = newPlayers; } public deletePlayer(playerId:string) { if(this.mPlayers.has(playerId)) { this.mPlayers.delete(playerId); } } } // 使用示例和辅助函数 function createMatchmaker(mPlayers: Map): Matchmaker { return new Matchmaker(mPlayers); } // 显示配对结果的辅助函数 function displayPairings(pairings: PairingResult[]): void { 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); } }); } export { Matchmaker, PairingResult, createMatchmaker, displayPairings };