Files
CardServer/MatchMaker.ts

224 lines
6.8 KiB
TypeScript
Raw Permalink Normal View History

2025-10-11 14:45:08 +08:00
import { Player } from "./Player";
interface PairingResult {
playerId: string;
opponentId: string;
isResting: boolean; // 是否为轮空玩家
}
class Matchmaker {
private mPlayers: Map<string, Player>;
private currentRound: number = 0;
private restingHistory: Map<string, number> = new Map(); // 记录每个玩家的轮空次数
2025-10-11 14:45:08 +08:00
constructor(players: Map<string, Player>) {
this.mPlayers = players;
}
/**
*
* @returns
*/
public generatePairings(): PairingResult[] {
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
}];
}
const pairings = this.createRandomPairings(playerIds);
2025-10-11 14:45:08 +08:00
this.currentRound++;
return pairings;
}
/**
* - 使 Fisher-Yates
2025-10-11 14:45:08 +08:00
* @param playerIds ID数组
* @returns
*/
private createRandomPairings(playerIds: string[]): PairingResult[] {
2025-10-11 14:45:08 +08:00
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);
}
// Fisher-Yates 洗牌算法 - O(n) 时间复杂度
const shuffled = this.shuffleArray(activePlayers);
2025-10-11 14:45:08 +08:00
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];
2025-10-11 14:45:08 +08:00
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;
}
/**
* 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;
}
2025-10-11 14:45:08 +08:00
/**
*
* @param playerIds ID数组
2025-10-11 14:45:08 +08:00
* @returns ID
*/
private selectRestingPlayer(playerIds: string[]): string {
// 初始化轮空次数记录
playerIds.forEach(id => {
if (!this.restingHistory.has(id)) {
this.restingHistory.set(id, 0);
}
2025-10-11 14:45:08 +08:00
});
// 找到轮空次数最少的玩家
2025-10-11 14:45:08 +08:00
let minRestingCount = Infinity;
playerIds.forEach(id => {
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 => {
const count = this.restingHistory.get(id) || 0;
2025-10-11 14:45:08 +08:00
return count === minRestingCount;
});
// 在轮空次数最少的玩家中随机选择
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 14:45:08 +08:00
* @param restingPlayer ID
* @param allPlayers ID
2025-10-11 14:45:08 +08:00
* @returns ID
*/
private selectGhostOpponent(restingPlayer: string, allPlayers: string[]): string {
// 只在当前存活的玩家中选择幽灵对手
const alivePlayers = allPlayers.filter(id => id !== restingPlayer);
2025-10-11 14:45:08 +08:00
if (alivePlayers.length === 0) {
return restingPlayer; // 如果没有其他存活玩家,返回自己
2025-10-11 14:45:08 +08:00
}
// 随机选择一个幽灵对手
const randomIndex = Math.floor(Math.random() * alivePlayers.length);
return alivePlayers[randomIndex];
2025-10-11 14:45:08 +08:00
}
/**
*
*/
public resetHistory(): void {
this.restingHistory.clear();
2025-10-11 14:45:08 +08:00
this.currentRound = 0;
}
/**
* 退
* @param newPlayers Map
*/
public updatePlayers(newPlayers: Map<string, Player>): 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);
}
}
2025-10-11 14:45:08 +08:00
}
public deletePlayer(playerId: string) {
if (this.mPlayers.has(playerId)) {
2025-10-11 14:45:08 +08:00
this.mPlayers.delete(playerId);
}
// 清理该玩家的轮空历史
this.restingHistory.delete(playerId);
2025-10-11 14:45:08 +08:00
}
}
// 使用示例和辅助函数
function createMatchmaker(mPlayers: Map<string, Player>): Matchmaker {
return new Matchmaker(mPlayers);
}
// 显示配对结果的辅助函数
function displayPairings(pairings: PairingResult[]): void {
console.log(`=== 第${pairings.length > 0 ? '当前' : '0'}轮配对结果 ===`);
const processedPairs = new Set<string>();
pairings.forEach(pairing => {
const pairKey = [pairing.playerId, pairing.opponentId].sort().join('-');
if (!processedPairs.has(pairKey)) {
console.log(`${pairing.playerId} VS ${pairing.opponentId}`);
2025-10-11 14:45:08 +08:00
processedPairs.add(pairKey);
}
});
}
export { Matchmaker, PairingResult, createMatchmaker, displayPairings };