namespace Highscore; /// /// Processing of gamer scores to determine an overall highscore /// public static class HighscoreProcessing { private const char Separator = ';'; /// /// Executes the program. /// public static void Run() { var scores = LoadScoresFromFile("Data/scores.csv"); var bestScores = FindBestScorePerUser(scores); SortGameScores(bestScores); WriteOutputFileAndPrint(bestScores, "Data/highscore.csv"); } /// /// Writes the provided instances to a file at the supplied path. /// The CSV format is Score;Date;Player. /// /// Game scores to persist /// Path to write to public static void WriteOutputFileAndPrint(GameScore[] highscore, string filePath) { string[] lines = new string[highscore.Length + 1]; lines[0] = "Score;Date;Player"; for (int i = 0; i < highscore.Length; i++) { lines[i + 1] = $"{highscore[i].Score};{highscore[i].Date:dd.MM.yyyy};{highscore[i].Player.NickName} (#{highscore[i].Player.Id})"; } File.WriteAllLines(filePath, lines); } /// /// Loads the CSV file at the supplied path and attempts to parse its content to /// create an array of instances utilizing . /// /// Path to read from /// Array of parsed items; empty array if file does not exist or is empty public static GameScore[] LoadScoresFromFile(string filePath) { if (!Path.Exists(filePath)) { return Array.Empty(); } string[] loadedScores = File.ReadAllLines(filePath); if (loadedScores.Length <= 1) { return Array.Empty(); } loadedScores = loadedScores[1..]; GameScore?[] gameScores = new GameScore[loadedScores.Length]; for (int i = 0; i < loadedScores.Length; i++) { if (TryParseGameScore(loadedScores[i], out GameScore? gameScore)) { if(gameScore!.Player.Id < 0 || gameScore.Score < 0) { gameScores[i] = null; continue; } gameScores[i] = gameScore!; } else { gameScores[i] = null; } } return TrimArray(gameScores); } /// /// Attempts to parse a CSV formatted string to a object. /// /// String to parse /// Successfully parsed object; null if parsing fails /// True if parsed successfully; false otherwise public static bool TryParseGameScore(string line, out GameScore? gameScore) { gameScore = null; string[] parts = line.Split(Separator); if (parts.Length != 4) { return false; } if (!int.TryParse(parts[0], out int id) || !DateTime.TryParse(parts[2], out DateTime date) || !int.TryParse(parts[3], out int score)) { return false; } if(parts[1] == string.Empty || score < 0) { return false; } gameScore = new GameScore(new Player(id, parts[1]), score, date); return true; } /// /// Based on the provided full set of game scores for each user the best score is determined. /// If a user has two or more entries with the same score the oldest (earliest) entry is considered to /// be the highest. /// /// Collection of all scores; may contain multiple entries for one user /// An array containing the highest score of each user, based on passed scores public static GameScore[] FindBestScorePerUser(GameScore[] allScores) { GameScore[] sortedScored = allScores; SortGameScores(sortedScored); sortedScored = TrimScorePerUser(sortedScored); return sortedScored; } private static GameScore[] TrimScorePerUser(GameScore?[] scores) { for (int i = 0; i < scores.Length; i++) { for (int j = 0; j < scores.Length; j++) { if (scores[i]?.Player.Id == scores[j]?.Player.Id && i != j) { if(scores[i] == null || scores[j] == null) { continue; } if (scores[i]!.Score < scores[j]!.Score || (scores[i]!.Score == scores[j]!.Score && scores[i]!.Date > scores[j]!.Date)) { (scores[i], scores[j]) = (scores[j], scores[i]); } scores[j] = null; } } } return TrimArray(scores); } /// /// Sorts the provided instances so that those with the /// highest score (older date is preferred if score is equal) are ranked first. /// Sorting happens in-place. /// /// Scores to sort public static void SortGameScores(GameScore[] scores) { for (int i = 0; i < scores.Length; i++) { for (int j = i + 1; j < scores.Length; j++) { if (scores[i].Score < scores[j].Score) { (scores[i], scores[j]) = (scores[j], scores[i]); } else if (scores[i].Score == scores[j].Score && scores[i].Date > scores[j].Date) { (scores[i], scores[j]) = (scores[j], scores[i]); } } } } /// /// Trims empty (null) entries from the provided array. /// /// The array to process /// A new array containing only the non-null entries from the passed array public static GameScore[] TrimArray(GameScore?[] arrayToTrim) { if (arrayToTrim.Length == 0) { return Array.Empty(); } int index = 0; foreach (var VARIABLE in arrayToTrim) { if (VARIABLE != null) { index++; } } if (index == 0) { return Array.Empty(); } var trimmedArray = new GameScore[index]; index = 0; for (int i = 0; i < arrayToTrim.Length; i++) { if (arrayToTrim[i] != null) { trimmedArray[index++] = arrayToTrim[i]!; } } return trimmedArray; } }