1.修复匹配时可能会匹配到死去玩家的逻辑
2.修复匹配的随机性条件,改为完全随机
This commit is contained in:
173
MatchMaker.js
173
MatchMaker.js
@@ -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];
|
pairings.push({
|
||||||
const player2 = rotatedPlayers[i + 1];
|
playerId: player1,
|
||||||
if (!usedPlayers.has(player1) && !usedPlayers.has(player2)) {
|
opponentId: player2,
|
||||||
pairings.push({
|
isResting: false
|
||||||
playerId: player1,
|
});
|
||||||
opponentId: player2,
|
pairings.push({
|
||||||
// socketID: this.mPlayers.get(player1)!.mSocketId,
|
playerId: player2,
|
||||||
isResting: false
|
opponentId: player1,
|
||||||
});
|
isResting: false
|
||||||
pairings.push({
|
});
|
||||||
playerId: player2,
|
|
||||||
opponentId: player1,
|
|
||||||
// socketID: this.mPlayers.get(player2)!.mSocketId,
|
|
||||||
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 => {
|
|
||||||
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 rotateAmount = this.currentRound % playerIds.length;
|
// 随机选择一个幽灵对手
|
||||||
return [
|
const randomIndex = Math.floor(Math.random() * alivePlayers.length);
|
||||||
...playerIds.slice(rotateAmount),
|
return alivePlayers[randomIndex];
|
||||||
...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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
203
MatchMaker.ts
203
MatchMaker.ts
@@ -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 pairings: PairingResult[] = [];
|
const shuffled = this.shuffleArray(activePlayers);
|
||||||
const usedPlayers = new Set<string>();
|
|
||||||
|
|
||||||
// 使用轮转算法确保配对的多样性
|
|
||||||
const rotatedPlayers = this.rotatePlayerOrder(activePlayers);
|
|
||||||
|
|
||||||
for (let i = 0; i < rotatedPlayers.length; i += 2) {
|
const pairings: PairingResult[] = [];
|
||||||
if (i + 1 < rotatedPlayers.length) {
|
|
||||||
const player1 = rotatedPlayers[i];
|
// 按顺序两两配对 - O(n) 时间复杂度
|
||||||
const player2 = rotatedPlayers[i + 1];
|
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({
|
||||||
pairings.push({
|
playerId: player1,
|
||||||
playerId: player1,
|
opponentId: player2,
|
||||||
opponentId: player2,
|
isResting: false
|
||||||
// socketID: this.mPlayers.get(player1)!.mSocketId,
|
});
|
||||||
isResting: false
|
pairings.push({
|
||||||
});
|
playerId: player2,
|
||||||
pairings.push({
|
opponentId: player1,
|
||||||
playerId: player2,
|
isResting: false
|
||||||
opponentId: player1,
|
});
|
||||||
// socketID: this.mPlayers.get(player2)!.mSocketId,
|
|
||||||
isResting: false
|
|
||||||
});
|
|
||||||
|
|
||||||
usedPlayers.add(player1);
|
|
||||||
usedPlayers.add(player2);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,124 +90,88 @@ interface PairingResult {
|
|||||||
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 洗牌后的新数组
|
||||||
|
*/
|
||||||
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据轮次旋转玩家顺序,增加配对的随机性
|
|
||||||
* @param playerIds 玩家ID数组
|
|
||||||
* @returns 旋转后的玩家数组
|
|
||||||
*/
|
|
||||||
private rotatePlayerOrder(playerIds: string[]): string[] {
|
|
||||||
if (playerIds.length <= 2) {
|
|
||||||
return [...playerIds];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const rotateAmount = this.currentRound % playerIds.length;
|
// 随机选择一个幽灵对手
|
||||||
return [
|
const randomIndex = Math.floor(Math.random() * alivePlayers.length);
|
||||||
...playerIds.slice(rotateAmount),
|
return alivePlayers[randomIndex];
|
||||||
...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} VS ${pairing.opponentId}`);
|
||||||
// console.log(`${pairing.playerId} 轮空 (对手: ${pairing.opponentId})`);
|
|
||||||
// } else {
|
|
||||||
console.log(`${pairing.playerId} VS ${pairing.opponentId}`);
|
|
||||||
// }
|
|
||||||
processedPairs.add(pairKey);
|
processedPairs.add(pairKey);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user