2025-10-11 14:45:08 +08:00
|
|
|
|
"use strict";
|
|
|
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
|
|
exports.Matchmaker = void 0;
|
|
|
|
|
exports.createMatchmaker = createMatchmaker;
|
|
|
|
|
exports.displayPairings = displayPairings;
|
|
|
|
|
class Matchmaker {
|
|
|
|
|
constructor(players) {
|
|
|
|
|
this.currentRound = 0;
|
2025-10-11 16:52:39 +08:00
|
|
|
|
this.restingHistory = new Map(); // 记录每个玩家的轮空次数
|
2025-10-11 14:45:08 +08:00
|
|
|
|
this.mPlayers = players;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 为当前轮次生成配对
|
|
|
|
|
* @returns 配对结果数组
|
|
|
|
|
*/
|
|
|
|
|
generatePairings() {
|
2025-10-11 16:52:39 +08:00
|
|
|
|
const playerIds = Array.from(this.mPlayers.keys()); // 只使用当前存活的玩家
|
2025-10-11 14:45:08 +08:00
|
|
|
|
const playerCount = playerIds.length;
|
|
|
|
|
if (playerCount === 0) {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
if (playerCount === 1) {
|
|
|
|
|
// 只有一个玩家时,与自己配对(轮空)
|
|
|
|
|
return [{
|
|
|
|
|
playerId: playerIds[0],
|
|
|
|
|
opponentId: playerIds[0],
|
|
|
|
|
isResting: true
|
|
|
|
|
}];
|
|
|
|
|
}
|
2025-10-11 16:52:39 +08:00
|
|
|
|
const pairings = this.createRandomPairings(playerIds);
|
2025-10-11 14:45:08 +08:00
|
|
|
|
this.currentRound++;
|
|
|
|
|
return pairings;
|
|
|
|
|
}
|
|
|
|
|
/**
|
2025-10-11 16:52:39 +08:00
|
|
|
|
* 创建随机配对 - 使用 Fisher-Yates 洗牌算法
|
2025-10-11 14:45:08 +08:00
|
|
|
|
* @param playerIds 玩家ID数组
|
|
|
|
|
* @returns 配对结果
|
|
|
|
|
*/
|
2025-10-11 16:52:39 +08:00
|
|
|
|
createRandomPairings(playerIds) {
|
2025-10-11 14:45:08 +08:00
|
|
|
|
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);
|
|
|
|
|
}
|
2025-10-11 16:52:39 +08:00
|
|
|
|
// Fisher-Yates 洗牌算法 - O(n) 时间复杂度
|
|
|
|
|
const shuffled = this.shuffleArray(activePlayers);
|
2025-10-11 14:45:08 +08:00
|
|
|
|
const pairings = [];
|
2025-10-11 16:52:39 +08:00
|
|
|
|
// 按顺序两两配对 - 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
|
|
|
|
|
});
|
2025-10-11 14:45:08 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 处理轮空玩家
|
|
|
|
|
if (restingPlayer) {
|
|
|
|
|
const ghostOpponent = this.selectGhostOpponent(restingPlayer, playerIds);
|
|
|
|
|
pairings.push({
|
|
|
|
|
playerId: restingPlayer,
|
|
|
|
|
opponentId: ghostOpponent,
|
|
|
|
|
isResting: true
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return pairings;
|
|
|
|
|
}
|
2025-10-11 16:52:39 +08:00
|
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
2025-10-11 14:45:08 +08:00
|
|
|
|
/**
|
|
|
|
|
* 选择轮空玩家(轮换制,确保每个玩家都有机会轮空)
|
2025-10-11 16:52:39 +08:00
|
|
|
|
* @param playerIds 玩家ID数组(当前存活的玩家)
|
2025-10-11 14:45:08 +08:00
|
|
|
|
* @returns 轮空玩家ID
|
|
|
|
|
*/
|
|
|
|
|
selectRestingPlayer(playerIds) {
|
2025-10-11 16:52:39 +08:00
|
|
|
|
// 初始化轮空次数记录
|
|
|
|
|
playerIds.forEach(id => {
|
|
|
|
|
if (!this.restingHistory.has(id)) {
|
|
|
|
|
this.restingHistory.set(id, 0);
|
|
|
|
|
}
|
2025-10-11 14:45:08 +08:00
|
|
|
|
});
|
2025-10-11 16:52:39 +08:00
|
|
|
|
// 找到轮空次数最少的玩家
|
2025-10-11 14:45:08 +08:00
|
|
|
|
let minRestingCount = Infinity;
|
|
|
|
|
playerIds.forEach(id => {
|
2025-10-11 16:52:39 +08:00
|
|
|
|
const count = this.restingHistory.get(id) || 0;
|
2025-10-11 14:45:08 +08:00
|
|
|
|
if (count < minRestingCount) {
|
|
|
|
|
minRestingCount = count;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
const candidatePlayers = playerIds.filter(id => {
|
2025-10-11 16:52:39 +08:00
|
|
|
|
const count = this.restingHistory.get(id) || 0;
|
2025-10-11 14:45:08 +08:00
|
|
|
|
return count === minRestingCount;
|
|
|
|
|
});
|
2025-10-11 16:52:39 +08:00
|
|
|
|
// 在轮空次数最少的玩家中随机选择
|
|
|
|
|
const selectedIndex = Math.floor(Math.random() * candidatePlayers.length);
|
|
|
|
|
const selectedPlayer = candidatePlayers[selectedIndex];
|
|
|
|
|
// 增加该玩家的轮空次数
|
|
|
|
|
this.restingHistory.set(selectedPlayer, (this.restingHistory.get(selectedPlayer) || 0) + 1);
|
|
|
|
|
return selectedPlayer;
|
2025-10-11 14:45:08 +08:00
|
|
|
|
}
|
|
|
|
|
/**
|
2025-10-11 16:52:39 +08:00
|
|
|
|
* 为轮空玩家选择一个"幽灵对手"(用于显示)
|
2025-10-11 14:45:08 +08:00
|
|
|
|
* @param restingPlayer 轮空玩家ID
|
2025-10-11 16:52:39 +08:00
|
|
|
|
* @param allPlayers 所有当前存活的玩家ID
|
2025-10-11 14:45:08 +08:00
|
|
|
|
* @returns 幽灵对手ID
|
|
|
|
|
*/
|
|
|
|
|
selectGhostOpponent(restingPlayer, allPlayers) {
|
2025-10-11 16:52:39 +08:00
|
|
|
|
// 只在当前存活的玩家中选择幽灵对手
|
|
|
|
|
const alivePlayers = allPlayers.filter(id => id !== restingPlayer);
|
|
|
|
|
if (alivePlayers.length === 0) {
|
|
|
|
|
return restingPlayer; // 如果没有其他存活玩家,返回自己
|
2025-10-11 14:45:08 +08:00
|
|
|
|
}
|
2025-10-11 16:52:39 +08:00
|
|
|
|
// 随机选择一个幽灵对手
|
|
|
|
|
const randomIndex = Math.floor(Math.random() * alivePlayers.length);
|
|
|
|
|
return alivePlayers[randomIndex];
|
2025-10-11 14:45:08 +08:00
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 重置配对历史(新游戏开始时调用)
|
|
|
|
|
*/
|
|
|
|
|
resetHistory() {
|
2025-10-11 16:52:39 +08:00
|
|
|
|
this.restingHistory.clear();
|
2025-10-11 14:45:08 +08:00
|
|
|
|
this.currentRound = 0;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 更新玩家列表(当有玩家加入或退出时调用)
|
|
|
|
|
* @param newPlayers 新的玩家Map
|
|
|
|
|
*/
|
|
|
|
|
updatePlayers(newPlayers) {
|
|
|
|
|
this.mPlayers = newPlayers;
|
2025-10-11 16:52:39 +08:00
|
|
|
|
// 清理已死亡玩家的轮空历史记录
|
|
|
|
|
const alivePlayerIds = new Set(newPlayers.keys());
|
|
|
|
|
for (const playerId of this.restingHistory.keys()) {
|
|
|
|
|
if (!alivePlayerIds.has(playerId)) {
|
|
|
|
|
this.restingHistory.delete(playerId);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-11 14:45:08 +08:00
|
|
|
|
}
|
|
|
|
|
deletePlayer(playerId) {
|
|
|
|
|
if (this.mPlayers.has(playerId)) {
|
|
|
|
|
this.mPlayers.delete(playerId);
|
|
|
|
|
}
|
2025-10-11 16:52:39 +08:00
|
|
|
|
// 清理该玩家的轮空历史
|
|
|
|
|
this.restingHistory.delete(playerId);
|
2025-10-11 14:45:08 +08:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
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)) {
|
|
|
|
|
console.log(`${pairing.playerId} VS ${pairing.opponentId}`);
|
|
|
|
|
processedPairs.add(pairKey);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|