private async Task ApplyBalanceResults(BalanceTeamsResult balance) { if (!IsNullOrEmpty(balance.Message)) { await SayBattle(balance.Message); } if ((balance.Players != null) && (balance.Players.Count > 0)) { foreach (var p in balance.Players) { UserBattleStatus u; if (Users.TryGetValue(p.Name, out u)) { u.IsSpectator = p.IsSpectator; u.AllyNumber = p.AllyID; } } foreach (var u in Users.Where(x => !balance.Players.Any(y => y.Name == x.Key))) { u.Value.IsSpectator = true; } } if (balance.DeleteBots) { foreach (var b in Bots.Keys) { await server.Broadcast(Users.Keys, new RemoveBot() { Name = b }); } Bots.Clear(); } if ((balance.Bots != null) && (balance.Bots.Count > 0)) { foreach (var p in balance.Bots) { Bots.AddOrUpdate(p.BotName, s => new BotBattleStatus(p.BotName, p.Owner ?? FounderName, p.BotAI) { AllyNumber = p.AllyID }, (s, status) => { status.AllyNumber = p.AllyID; status.owner = p.Owner ?? FounderName; status.aiLib = p.BotAI; status.Name = p.BotName; return(status); }); } } foreach (var u in Users.Values.Select(x => x.ToUpdateBattleStatus()).ToList()) { await server.Broadcast(Users.Keys, u); // send other's status to self } foreach (var u in Bots.Values.Select(x => x.ToUpdateBotStatus()).ToList()) { await server.Broadcast(Users.Keys, u); } }
public static BalanceTeamsResult BalanceTeams(string autoHost, string map, string mod,List<AccountTeam> currentTeams, List<BotTeam> currentBots) { var mode = ContentService.GetModeFromHost(autoHost); if (currentTeams.Count < 1) return new BalanceTeamsResult(); using (var db = new ZkDataContext()) { var res = new BalanceTeamsResult(); res.Message = ""; var idList = currentTeams.Select(x => x.AccountID).ToList(); var players = new List<Account>(); foreach (var p in idList.Select(x => db.Accounts.First(y => y.LobbyID == x))) { /*if (p.ClanID == null) { //res.Message += string.Format("{0} cannot play, must join a clan first http://zero-k.info/Planetwars/ClanList\n", p.Name); //AuthServiceClient.SendLobbyMessage(p, "To play here, join a clan first http://zero-k.info/Planetwars/ClanList"); }*/ /*if (p.Clan != null && !p.Name.Contains(p.Clan.Shortcut)) { res.Message += string.Format("{0} cannot play, name must contain clan tag {1}\n", p.Name, p.Clan.Shortcut); AuthServiceClient.SendLobbyMessage(p, string.Format( "Your name must contain clan tag {0}, rename for example by saying: /rename [{0}]{1}", p.Clan.Shortcut, p.Name)); }*/ if (p.Level < GlobalConst.MinPlanetWarsLevel) { res.Message += string.Format("{0} cannot play, his level is {1}, minimum level is {2}\n", p.Name, p.Level, GlobalConst.MinPlanetWarsLevel); AuthServiceClient.SendLobbyMessage(p, string.Format( "Sorry, PlanetWars is competive online campaign for experienced players. You need to be at least level 5 to play here. To increase your level, play more games on other hosts or open multiplayer game and play against computer AI bots. You can observe this game however.")); } else players.Add(p); } var clans = players.Where(x => x.Clan != null).Select(x => x.Clan).ToList(); var treaties = new Dictionary<Tuple<Clan, Clan>, EffectiveTreaty>(); var planet = db.Galaxies.Single(x => x.IsDefault).Planets.Single(x => x.Resource.InternalName == map); // bots game if (planet.PlanetStructures.Any(x => !string.IsNullOrEmpty(x.StructureType.EffectBots))) { var teamID = 0; for (var i = 0; i < players.Count; i++) res.BalancedTeams.Add(new AccountTeam() { AccountID = players[i].LobbyID ?? 0, Name = players[i].Name, AllyID = 0, TeamID = teamID++ }); int cnt = 1; foreach (var b in planet.PlanetStructures.Select(x => x.StructureType).Where(x => !string.IsNullOrEmpty(x.EffectBots))) res.Bots.Add(new BotTeam() { AllyID = 1, BotAI = b.EffectBots, TeamID = teamID++, BotName = "Aliens" + cnt++}); res.Message += string.Format("This planet is infested by aliens, fight for your survival"); return res; } var planetFactionId = planet.Account != null ? planet.Account.FactionID ?? 0 : 0; var attackerFactions = planet.AccountPlanets.Where(x => x.DropshipCount > 0 && x.Account.FactionID != null).Select(x => (x.Account.FactionID ?? 0)). Distinct().ToList(); if (currentTeams.Count < 2) return new BalanceTeamsResult() { Message = "Not enough players" }; for (var i = 1; i < clans.Count; i++) { for (var j = 0; j < i; j++) { var treaty = clans[i].GetEffectiveTreaty(clans[j]); treaties[Tuple.Create(clans[i], clans[j])] = treaty; treaties[Tuple.Create(clans[j], clans[i])] = treaty; // if treaty is neutral but they send ships - mark as "war" if (planet.OwnerAccountID != null && treaty.AllyStatus == AllyStatus.Neutral) { if (clans[i].ClanID == planet.Account.ClanID && planet.AccountPlanets.Any(x => x.Account.ClanID == clans[j].ClanID && x.DropshipCount > 0)) treaty.AllyStatus = AllyStatus.War; else if (clans[j].ClanID == planet.Account.ClanID && planet.AccountPlanets.Any(x => x.Account.ClanID == clans[i].ClanID && x.DropshipCount > 0)) treaty.AllyStatus = AllyStatus.War; } } } var sameTeamScore = new double[players.Count,players.Count]; for (var i = 1; i < players.Count; i++) { for (var j = 0; j < i; j++) { var c1 = players[i].Clan; var c2 = players[j].Clan; var f1 = players[i].FactionID ?? -1; var f2 = players[i].FactionID ?? -1; var points = 0.0; if (players[i].FactionID != null && players[i].FactionID == players[j].FactionID) points = 3; // same faction weight 1 if (c1 != null && c2 != null) { if (c1 == c2) points = 4; else { var treaty = treaties[Tuple.Create(players[i].Clan, players[j].Clan)]; if (treaty.AllyStatus == AllyStatus.Alliance) points = 2; else if (treaty.AllyStatus == AllyStatus.Ceasefire) points = 1; else if (treaty.AllyStatus == AllyStatus.War) points = -3; if (treaty.AllyStatus == AllyStatus.Neutral && f1 != f2) if ((planetFactionId == f1 && attackerFactions.Contains(f2)) || (planetFactionId == f2 && attackerFactions.Contains(f1))) points = -3; } } else if (f1 != f2) if ((planetFactionId == f1 && attackerFactions.Contains(f2)) || (planetFactionId == f2 && attackerFactions.Contains(f1))) points = -3; sameTeamScore[i, j] = points; sameTeamScore[j, i] = points; //res.Message += string.Format("{0} + {1} = {2} \n", players[i].Name, players[j].Name, points); } } var playerScoreMultiplier = new double[players.Count]; for (var i = 0; i < players.Count; i++) { var mult = 1.0; var player = players[i]; if (planet.OwnerAccountID == player.AccountID) mult += 1; // owner else if (planet.Account != null && planet.Account.ClanID == player.AccountID) mult += 0.5; // owner's clan if (planet.AccountPlanets.Any(x => x.AccountID == player.AccountID && x.DropshipCount > 0)) mult += 1; // own dropship else if (planet.AccountPlanets.Any(x => x.DropshipCount > 0 && x.Account.ClanID == player.ClanID)) mult += 0.5; // clan's dropship playerScoreMultiplier[i] = mult; //res.Message += string.Format("{0} mult = {1} \n", players[i].Name, mult); } var limit = 1 << (players.Count); var bestCombination = -1; var bestScore = double.MinValue; double bestCompo = 0; double absCompo = 0; double bestElo = 0; double bestTeamDiffs = 0; var playerAssignments = new int[players.Count]; for (var combinator = 0; combinator < limit; combinator++) { //double team0Weight = 0; double team0Elo = 0; //double team1Weight = 0; double team1Elo = 0; var team0count = 0; var team1count = 0; // determine where each player is amd dp some adding for (var i = 0; i < players.Count; i++) { var player = players[i]; var team = (combinator & (1 << i)) > 0 ? 1 : 0; playerAssignments[i] = team; if (team == 0) { team0Elo += player.EffectiveElo; //team0Weight += player.EloWeight; team0count++; } else { team1Elo += player.EffectiveElo; // *player.EloWeight; //team1Weight += player.EloWeight; team1count++; } } if (team0count == 0 || team1count == 0) continue; // skip combination, empty team // calculate score for team difference var teamDiffScore = -(20.0*Math.Abs(team0count - team1count)/(double)(team0count + team1count)) - Math.Abs(team0count - team1count); if (teamDiffScore < -10) continue; // max imabalance double balanceModifier = 0; // count elo vs balance modifier /* if (team0count < team1count) balanceModifier = -teamDiffScore; else balanceModifier = teamDiffScore;*/ // calculate score for elo difference team0Elo = team0Elo/team0count; team1Elo = team1Elo/team1count; //team0Elo = team0Elo/team0Weight; //team1Elo = team1Elo/team1Weight; var eloScore = -Math.Abs(team0Elo - team1Elo)/14; if (eloScore < -17) continue; if (team0Elo < team1Elo) balanceModifier += -eloScore; else balanceModifier += eloScore; // verify if ther eis sense in playing (no zero sum game ip abuse) var majorityFactions = (from teamData in Enumerable.Range(0, players.Count).GroupBy(x => playerAssignments[x]) let majorityCount = Math.Ceiling(teamData.Count()/2.0) select teamData.GroupBy(x => players[x].FactionID).Where(x => x.Key != null && x.Count() >= majorityCount). Select(x => x.Key ?? 0)).ToList(); if (majorityFactions.Count == 2 && majorityFactions[0].Intersect(majorityFactions[1]).Any()) continue; // winning either side would be benefitial for some majority faction // calculate score for meaningfull teams var compoScore = 0.0; for (var i = 0; i < players.Count; i++) // for every player calculate his score as average of relations to other plaeyrs { double sum = 0; var cnt = 0; for (var j = 0; j < players.Count; j++) { if (i != j) { var sts = sameTeamScore[i, j]; if (sts != 0.0) // we only consider no-neutral people { if (playerAssignments[i] == playerAssignments[j]) { sum += sts; cnt++; } /*else sum -= sts; // different teams - score is equal to negation of same team score cnt++;*/ } } } if (cnt > 0) // player can be meaningfully ranked, he had at least one non zero relation compoScore += playerScoreMultiplier[i]*sum/cnt; } if (compoScore < 0) continue; // get meaningfull teams only || compoScore < 0.5*absCompo if (compoScore > absCompo) absCompo = compoScore; // todo lame - abs compo not known at this point,should be 2 pass var score = -Math.Abs(balanceModifier) + teamDiffScore + compoScore; if (score > bestScore) { bestCombination = combinator; bestScore = score; bestElo = eloScore; bestCompo = compoScore; bestTeamDiffs = teamDiffScore; } } if (bestCombination == -1) { res.BalancedTeams = null; res.Message += "Cannot be balanced well at this point"; } /*else if (bestCompo < absCompo*0.5) { res.BalancedTeams = null; res.Message += string.Format("Cannot be balanced well at this point - best composition: {0}, available: {1}", absCompo, bestCompo); }*/ else { var differs = false; for (var i = 0; i < players.Count; i++) { var allyID = ((bestCombination & (1 << i)) > 0) ? 1 : 0; if (!differs && allyID != currentTeams.First(x => x.AccountID == players[i].LobbyID).AllyID) differs = true; res.BalancedTeams.Add(new AccountTeam() { AccountID = players[i].LobbyID.Value, Name = players[i].Name, AllyID = allyID, TeamID = i }); } if (differs) { res.Message += string.Format( "Winning combination score: {0:0.##} team difference, {1:0.##} elo, {2:0.##} composition. Win chance {3}%", bestTeamDiffs, bestElo, bestCompo, Utils.GetWinChancePercent(bestElo*20)); } } res.DeleteBots = true; return res; } }