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;
}
}