A game about forced loneliness, made by TACStudios
at master 192 lines 9.7 kB view raw
1using System; 2using System.Collections.Generic; 3using Unity.Multiplayer.Center.Common; 4using Unity.Multiplayer.Center.Questionnaire; 5using UnityEngine; 6 7namespace Unity.Multiplayer.Center.Recommendations 8{ 9 using AnswerWithQuestion = Tuple<Question, Answer>; 10 11 /// <summary> 12 /// Builds recommendation views based on Questionnaire data and Answer data. 13 /// The recommendation is based on the scoring of the answers, which is controlled by the RecommenderSystemData. 14 /// </summary> 15 internal static class RecommenderSystem 16 { 17 /// <summary> 18 /// Main entry point for the recommender system: computes the recommendation based on the questionnaire data and 19 /// the answers. 20 /// If no answer has been given or the questionnaire does not match the answers, this returns null. 21 /// </summary> 22 /// <param name="questionnaireData">The questionnaire that the user filled.</param> 23 /// <param name="answerData">The answers the user gave.</param> 24 /// <returns>The recommendation view data.</returns> 25 public static RecommendationViewData GetRecommendation(QuestionnaireData questionnaireData, AnswerData answerData) 26 { 27 var answers = CollectAnswers(questionnaireData, answerData); 28 29 // Note: valid now only because we do not have multiple answers per question 30 if (answers.Count < questionnaireData.Questions.Length) return null; 31 32 var data = RecommenderSystemDataObject.instance.RecommenderSystemData; 33 var scoredSolutions = CalculateScore(data, answers); 34 35 return CreateRecommendation(data, scoredSolutions); 36 } 37 38 /// <summary> 39 /// Get the view data for all possible solution selections. 40 /// </summary> 41 /// <returns>The constructed set of views</returns> 42 public static SolutionsToRecommendedPackageViewData GetSolutionsToRecommendedPackageViewData() 43 { 44 var data = RecommenderSystemDataObject.instance.RecommenderSystemData; 45 var installedPackageDictionary = PackageManagement.InstalledPackageDictionary(); 46 var selections = new SolutionSelection[16]; 47 var packages = new RecommendedPackageViewData[16][]; 48 PossibleSolution[] netcodes = { PossibleSolution.NGO, PossibleSolution.N4E, PossibleSolution.CustomNetcode, PossibleSolution.NoNetcode }; 49 PossibleSolution[] hostings = { PossibleSolution.LS, PossibleSolution.DS, PossibleSolution.CloudCode, PossibleSolution.DA }; 50 51 var index = 0; 52 foreach (var netcode in netcodes) 53 { 54 foreach (var hosting in hostings) 55 { 56 var selection = new SolutionSelection(netcode, hosting); 57 selections[index] = selection; 58 packages[index] = BuildRecommendationForSelection(data, selection, installedPackageDictionary); 59 60 ++index; 61 } 62 } 63 64 return new SolutionsToRecommendedPackageViewData(selections, packages); 65 } 66 67 public static void AdaptRecommendationToNetcodeSelection(RecommendationViewData recommendation) 68 { 69 RecommendationUtils.MarkIncompatibleHostingModels(recommendation); 70 var maxIndex = RecommendationUtils.IndexOfMaximumScore(recommendation.ServerArchitectureOptions); 71 recommendation.ServerArchitectureOptions[maxIndex].RecommendationType = RecommendationType.MainArchitectureChoice; 72 if (RecommendationUtils.GetSelectedHostingModel(recommendation) == null) 73 recommendation.ServerArchitectureOptions[maxIndex].Selected = true; 74 } 75 76 static List<AnswerWithQuestion> CollectAnswers(QuestionnaireData questionnaireData, AnswerData answerData) 77 { 78 if (questionnaireData?.Questions == null || questionnaireData.Questions.Length == 0) 79 throw new ArgumentException("Questionnaire data is null or empty", nameof(questionnaireData)); 80 81 List<AnswerWithQuestion> givenAnswers = new(); 82 83 var answers = answerData.Answers; 84 85 foreach (var answeredQuestion in answers) 86 { 87 // find question for the answer 88 if (!Logic.TryGetQuestionByQuestionId(questionnaireData, answeredQuestion.QuestionId, out var question)) 89 continue; 90 91 // find answer object for the given answer id 92 foreach (var answerId in answeredQuestion.Answers) 93 { 94 if (!Logic.TryGetAnswerByAnswerId(question, answerId, out var choice)) 95 continue; 96 givenAnswers.Add(Tuple.Create(question, choice)); 97 } 98 } 99 100 return givenAnswers; 101 } 102 103 static Dictionary<PossibleSolution, Scoring> CalculateScore(RecommenderSystemData data, List<AnswerWithQuestion> answers) 104 { 105 var possibleSolutions = Enum.GetValues(typeof(PossibleSolution)); 106 Dictionary<PossibleSolution, Scoring> scores = new(possibleSolutions.Length); 107 108 foreach (var solution in possibleSolutions) 109 { 110 var solutionObject = data.SolutionsByType[(PossibleSolution) solution]; 111 scores.Add((PossibleSolution) solution, new Scoring(solutionObject.ShortDescription)); 112 } 113 114 foreach (var (question, answer) in answers) 115 { 116 foreach (var scoreImpact in answer.ScoreImpacts) 117 { 118 scores[scoreImpact.Solution].AddScore(scoreImpact.Score * question.GlobalWeight, scoreImpact.Comment); 119 } 120 } 121 122 return scores; 123 } 124 125 static RecommendationViewData CreateRecommendation(RecommenderSystemData data, IReadOnlyDictionary<PossibleSolution, Scoring> scoredSolutions) 126 { 127 RecommendationViewData recommendation = new(); 128 var installedPackageDictionary = PackageManagement.InstalledPackageDictionary(); 129 130 recommendation.NetcodeOptions = BuildRecommendedSolutions(data, new [] { 131 (PossibleSolution.NGO, scoredSolutions[PossibleSolution.NGO]), 132 (PossibleSolution.N4E, scoredSolutions[PossibleSolution.N4E]), 133 (PossibleSolution.CustomNetcode, scoredSolutions[PossibleSolution.CustomNetcode]), 134 (PossibleSolution.NoNetcode, scoredSolutions[PossibleSolution.NoNetcode]) }, 135 installedPackageDictionary); 136 137 recommendation.ServerArchitectureOptions = BuildRecommendedSolutions(data, new [] { 138 (PossibleSolution.LS, scoredSolutions[PossibleSolution.LS]), 139 (PossibleSolution.DS, scoredSolutions[PossibleSolution.DS]), 140 (PossibleSolution.CloudCode, scoredSolutions[PossibleSolution.CloudCode]), 141 (PossibleSolution.DA, scoredSolutions[PossibleSolution.DA]) }, 142 installedPackageDictionary); 143 144 AdaptRecommendationToNetcodeSelection(recommendation); 145 return recommendation; 146 } 147 148 static RecommendedSolutionViewData[] BuildRecommendedSolutions(RecommenderSystemData data, (PossibleSolution, Scoring)[] scoredSolutions, Dictionary<string, string> installedPackageDictionary) 149 { 150 var recommendedSolution = RecommendationUtils.FindRecommendedSolution(scoredSolutions); 151 var result = new RecommendedSolutionViewData[scoredSolutions.Length]; 152 153 for (var index = 0; index < scoredSolutions.Length; index++) 154 { 155 var scoredSolution = scoredSolutions[index]; 156 var recoType = scoredSolution.Item1 == recommendedSolution ? RecommendationType.MainArchitectureChoice : RecommendationType.SecondArchitectureChoice; 157 var reco = new RecommendedSolutionViewData(data, data.SolutionsByType[scoredSolution.Item1], recoType, scoredSolution.Item2, installedPackageDictionary); 158 result[index] = reco; 159 } 160 161 return result; 162 } 163 164 static RecommendedPackageViewData[] BuildRecommendationForSelection(RecommenderSystemData data, SolutionSelection selection, Dictionary<string, string> installedPackageDictionary) 165 { 166 // Note: working on a copy that we modify 167 var netcodePackages = (RecommendedPackage[]) data.SolutionsByType[selection.Netcode].RecommendedPackages.Clone(); 168 var hostingOverrides = data.SolutionsByType[selection.HostingModel].RecommendedPackages; 169 foreach (var package in hostingOverrides) 170 { 171 var existing = Array.FindIndex(netcodePackages, p => p.PackageId == package.PackageId); 172 if (existing == -1) 173 { 174 Debug.LogError($"Malformed data for hosting model {selection.HostingModel}: package {package.PackageId} not found in netcode packages of {selection.Netcode}."); 175 continue; 176 } 177 178 netcodePackages[existing] = package; 179 } 180 181 var result = new RecommendedPackageViewData[netcodePackages.Length]; 182 for (var index = 0; index < netcodePackages.Length; index++) 183 { 184 var package = netcodePackages[index]; 185 installedPackageDictionary.TryGetValue(package.PackageId, out var installedVersion); 186 result[index] = new RecommendedPackageViewData( data.PackageDetailsById[package.PackageId], package, installedVersion); 187 } 188 189 return result; 190 } 191 } 192}