private bool IsBetterThan(TrialGroupConfiguration other) { if (m_groupsWithConflictingGenders < other.m_groupsWithConflictingGenders) { return(true); } if (m_groupsWithConflictingGenders > other.m_groupsWithConflictingGenders) { return(false); } return(MinimumProximity > other.MinimumProximity); }
internal static TrialGroupConfiguration Best(List <TrialGroupConfiguration> configurations, TrialGroupConfiguration previousBest) { foreach (var configuration in configurations) { configuration.CalculateConflicts(); } var best = previousBest ?? configurations.First(); foreach (var config in configurations.Skip(previousBest == null ? 1 : 0).Where(config => config.IsBetterThan(best))) { best = config; } return(best); }
private void AddCharacterToBestGroup(CharacterDetail characterDetail, TrialGroupConfiguration configuration) { List <CharacterGroup> groups; IEnumerable <CharacterGroup> availableGroups = configuration.Groups.Where(g => !g.Closed && !configuration.IsGroupReservedForNarratorOrExtraBiblical(g)); if (!availableGroups.Any()) { availableGroups = configuration.Groups.Where(g => !g.Closed); } var groupMatchQualityDictionary = new Dictionary <MatchQuality, List <CharacterGroup> >(configuration.Groups.Count); foreach (var characterGroup in availableGroups) { var voiceActor = characterGroup.VoiceActor; var quality = new MatchQuality(voiceActor.GetGenderMatchQuality(characterDetail), voiceActor.GetAgeMatchQuality(characterDetail)); if (!groupMatchQualityDictionary.TryGetValue(quality, out groups)) { groupMatchQualityDictionary[quality] = groups = new List <CharacterGroup>(); } groups.Add(characterGroup); } var groupToProximityDict = new Dictionary <CharacterGroup, WeightedMinimumProximity>(); if (groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Perfect, AgeMatchQuality.Perfect), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict); } if (!groupToProximityDict.Any(i => i.Value.WeightedNumberOfBlocks >= Proximity.kDefaultMinimumProximity) && groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Perfect, AgeMatchQuality.CloseAdult), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict); } if (!groupToProximityDict.Any(i => i.Value.WeightedNumberOfBlocks >= Proximity.kDefaultMinimumProximity || i.Value.NumberOfBlocks >= configuration.MinimumProximity) && groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Perfect, AgeMatchQuality.AdultVsChild), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict); } if (!groupToProximityDict.Any(i => i.Value.WeightedNumberOfBlocks >= Proximity.kDefaultMinimumProximity || i.Value.NumberOfBlocks >= configuration.MinimumProximity) && groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Acceptable, AgeMatchQuality.Perfect), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict, 1.1); } if (!groupToProximityDict.Any(i => i.Value.WeightedNumberOfBlocks >= Proximity.kDefaultMinimumProximity || i.Value.NumberOfBlocks >= configuration.MinimumProximity) && groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Acceptable, AgeMatchQuality.CloseAdult), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict, 1.1); } if (!groupToProximityDict.Any(i => i.Value.WeightedNumberOfBlocks >= Proximity.kDefaultMinimumProximity || i.Value.NumberOfBlocks >= configuration.MinimumProximity) && groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Acceptable, AgeMatchQuality.AdultVsChild), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict, 1.1); } if (!groupToProximityDict.Any(i => i.Value.WeightedNumberOfBlocks >= Proximity.kDefaultMinimumProximity || i.Value.NumberOfBlocks >= configuration.MinimumProximity) && groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Mismatch, AgeMatchQuality.Perfect), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict, 2.3); } if (!groupToProximityDict.Any(i => i.Value.WeightedNumberOfBlocks >= Proximity.kDefaultMinimumProximity || i.Value.NumberOfBlocks >= configuration.MinimumProximity) && groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Mismatch, AgeMatchQuality.CloseAdult), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict, 2.4); } if (!groupToProximityDict.Any(i => i.Value.WeightedNumberOfBlocks >= Proximity.kDefaultMinimumProximity || i.Value.NumberOfBlocks >= configuration.MinimumProximity) && groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Mismatch, AgeMatchQuality.AdultVsChild), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict, 2.5); } if (!groupToProximityDict.Any(i => i.Value.WeightedNumberOfBlocks >= Proximity.kDefaultMinimumProximity || i.Value.NumberOfBlocks >= configuration.MinimumProximity) && groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Perfect, AgeMatchQuality.Mismatch), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict, 2.7); } if (!groupToProximityDict.Any(i => i.Value.WeightedNumberOfBlocks >= Proximity.kDefaultMinimumProximity || i.Value.NumberOfBlocks >= configuration.MinimumProximity) && groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Acceptable, AgeMatchQuality.Mismatch), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict, 2.9); } if (!groupToProximityDict.Any(i => i.Value.WeightedNumberOfBlocks >= Proximity.kDefaultMinimumProximity || i.Value.NumberOfBlocks >= configuration.MinimumProximity) && groupMatchQualityDictionary.TryGetValue(new MatchQuality(GenderMatchQuality.Mismatch, AgeMatchQuality.Mismatch), out groups)) { CalculateProximityForGroups(characterDetail, groups, groupToProximityDict, 3.2); } var bestGroupEntry = groupToProximityDict.Aggregate((l, r) => l.Value.WeightedNumberOfBlocks > r.Value.WeightedNumberOfBlocks ? l : r); var bestGroup = bestGroupEntry.Key; if (configuration.MinimumProximity > bestGroupEntry.Value.NumberOfBlocks) { // We're adding the character to the best group we could find, but it is now the *worst* group in the configuration. configuration.NoteGroupWithWorstProximity(bestGroupEntry.Key, bestGroupEntry.Value.NumberOfBlocks); } bestGroup.CharacterIds.Add(characterDetail.CharacterId); if (RelatedCharactersData.Singleton.GetCharacterIdsForType(CharacterRelationshipType.SameCharacterWithMultipleAges).Contains(characterDetail.CharacterId)) { foreach (var relatedCharacters in RelatedCharactersData.Singleton.GetCharacterIdToRelatedCharactersDictionary()[characterDetail.CharacterId]) { bestGroup.CharacterIds.AddRange(relatedCharacters.CharacterIds); } } }
internal List <CharacterGroup> GenerateCharacterGroups() { List <CharacterGroup> characterGroups = CreateGroupsForActors(m_project.VoiceActorList.Actors).ToList(); if (characterGroups.Count == 0) { return(characterGroups); // REVIEW: Maybe we should throw an exception instead. } m_project.SetDefaultCharacterGroupGenerationPreferences(); List <VoiceActor.VoiceActor> nonCameoActors = m_project.VoiceActorList.Actors.Where(a => !a.IsCameo).ToList(); if (nonCameoActors.Count == 0) { return(characterGroups); // All cameo actors! This should never happen. } var sortedDict = from entry in m_keyStrokesByCharacterId orderby entry.Value descending select entry; IReadOnlyDictionary <string, CharacterDetail> characterDetails = m_project.AllCharacterDetailDictionary; var includedCharacterDetails = characterDetails.Values.Where(c => sortedDict.Select(e => e.Key).Contains(c.CharacterId)).ToList(); // In the first loop, we're looking for actors that could only possibly play one character role. // Since we're not doing strict age matching, this is most likely only to find any candidates in // the case of children (and then only if the project includes a limited selection of books) var characterDetailsUniquelyMatchedToActors = new Dictionary <CharacterDetail, List <VoiceActor.VoiceActor> >(); foreach (var actor in nonCameoActors) { // After we find the second match, we can quit looking because we're only interested in unique matches. var matches = includedCharacterDetails.Where(c => !CharacterVerseData.IsCharacterStandard(c.CharacterId) && actor.Matches(c)).Take(2).ToList(); if (matches.Any()) { if (matches.Count == 1) { var characterDetail = matches.First(); if (characterDetailsUniquelyMatchedToActors.ContainsKey(characterDetail)) { characterDetailsUniquelyMatchedToActors[characterDetail].Add(actor); } else { characterDetailsUniquelyMatchedToActors[characterDetail] = new List <VoiceActor.VoiceActor> { actor } }; } } } // This loop uses the results of the previous one to add the characters to a group, and to close that // group to further additions. If there's more than one candidate actor, we pick one arbitrarily, // since afterwards we'll be clearing actor names anyway. // Since all groups now have an actor assigned up-front, at the // end of the generation process, we'll need to clear all actor assignments except for the ones that // are pre-determined here. List <int> actorsWithRealAssignments = new List <int>(); foreach (var characterDetailToActors in characterDetailsUniquelyMatchedToActors) { var matchingActors = characterDetailToActors.Value; var matchingGroups = characterGroups.Where(g => matchingActors.Any(a => a.Id == g.VoiceActorId)).ToList(); matchingGroups.First().CharacterIds.Add(characterDetailToActors.Key.CharacterId); if (matchingGroups.Count == 1) { actorsWithRealAssignments.Add(matchingGroups[0].VoiceActorId); } foreach (var characterGroup in matchingGroups) { characterGroup.Closed = true; } } // TODO: Make sure we didn't close all the groups (unless we assigned all the character IDs foreach (var character in includedCharacterDetails) { var matchingActors = characterGroups.Where(g => !g.Closed).Select(g => g.VoiceActor).Where(a => a.Matches(character)).ToList(); if (matchingActors.Count == 1) { var matchingActor = matchingActors.First(); CharacterGroup groupForActor = characterGroups.Single(g => g.VoiceActorId == matchingActor.Id); groupForActor.CharacterIds.Add(character.CharacterId); actorsWithRealAssignments.Add(matchingActor.Id); } } int maxMaleNarrators = m_project.CharacterGroupGenerationPreferences.NumberOfMaleNarrators; int maxFemaleNarrators = m_project.CharacterGroupGenerationPreferences.NumberOfFemaleNarrators; TrialGroupConfiguration bestConfiguration = null; do { var trialConfigurationsForNarratorsAndExtras = TrialGroupConfiguration.GeneratePossibilities(characterGroups, ref maxMaleNarrators, ref maxFemaleNarrators, includedCharacterDetails, m_project); if (trialConfigurationsForNarratorsAndExtras.Any()) { foreach (var configuration in trialConfigurationsForNarratorsAndExtras) { foreach (var entry in sortedDict) { string characterId = entry.Key; if (configuration.Groups.Any(g => g.CharacterIds.Contains(characterId))) { continue; } CharacterDetail characterDetail; if (!characterDetails.TryGetValue(characterId, out characterDetail)) { if (characterId == CharacterVerseData.AmbiguousCharacter || characterId == CharacterVerseData.UnknownCharacter) { continue; // This should never happen in production code! } //Debug.WriteLine("No character details for unexpected character ID (see PG-): " + characterId); //continue; throw new KeyNotFoundException("No character details for unexpected character ID (see PG-471): " + characterId); } if (!configuration.AddToReservedGroupIfAppropriate(characterId)) { AddCharacterToBestGroup(characterDetail, configuration); } } } bestConfiguration = TrialGroupConfiguration.Best(trialConfigurationsForNarratorsAndExtras, bestConfiguration); if (bestConfiguration.MinimumProximity >= Proximity.kDefaultMinimumProximity) { return(GetFinalizedGroups(bestConfiguration.Groups, actorsWithRealAssignments)); } } if (maxMaleNarrators == 0) { maxFemaleNarrators--; } else if (maxFemaleNarrators == 0) { maxMaleNarrators--; } else if (bestConfiguration != null && (bestConfiguration.GroupWithWorstProximity.ContainsCharacterWithGender(CharacterGender.Female) || bestConfiguration.GroupWithWorstProximity.ContainsCharacterWithGender(CharacterGender.PreferFemale))) { maxFemaleNarrators--; } else if (bestConfiguration != null && (bestConfiguration.GroupWithWorstProximity.ContainsCharacterWithGender(CharacterGender.Male) || bestConfiguration.GroupWithWorstProximity.ContainsCharacterWithGender(CharacterGender.PreferMale))) { maxMaleNarrators--; } else if (maxMaleNarrators > maxFemaleNarrators) { maxMaleNarrators--; } else { maxFemaleNarrators--; } } while (maxMaleNarrators + maxFemaleNarrators > 0); Debug.Assert(bestConfiguration != null); return(GetFinalizedGroups(bestConfiguration.Groups, actorsWithRealAssignments)); }