diff --git a/projects/AstroBalance/Assets/Scripts/RocketLaunch/LaunchControl.cs b/projects/AstroBalance/Assets/Scripts/RocketLaunch/LaunchControl.cs index a2f51b52..6e7ef37d 100644 --- a/projects/AstroBalance/Assets/Scripts/RocketLaunch/LaunchControl.cs +++ b/projects/AstroBalance/Assets/Scripts/RocketLaunch/LaunchControl.cs @@ -64,7 +64,7 @@ void Start() tracker = FindFirstObjectByType(); SaveData saveData = new(saveFilename); - RocketLaunchData lastGameData = saveData.GetLastGameData(); + RocketLaunchData lastGameData = saveData.GetLastCompleteGameData(); if (lastGameData == null) { diff --git a/projects/AstroBalance/Assets/Scripts/SaveData.cs b/projects/AstroBalance/Assets/Scripts/SaveData.cs index d51c487f..39bbfeca 100644 --- a/projects/AstroBalance/Assets/Scripts/SaveData.cs +++ b/projects/AstroBalance/Assets/Scripts/SaveData.cs @@ -1,6 +1,9 @@ +using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; +using System.Text; using UnityEngine; /// @@ -9,22 +12,21 @@ /// The type of game data (specific to each mini-game) [System.Serializable] public class SaveData - where T : GameData + where T : GameData, new() { - public List savedGames = new List(); + private bool saveFileExists = false; private string dataPath; /// /// Create a new Save Data collection. If available, this will - /// be populated with previous save data from filename.json + /// build on previous save data from filename.csv /// /// The filename of the save file public SaveData(string filename) { // currently defaults to "C:\Users\username\AppData\LocalLow\DefaultCompany\AstroBalance" on Windows - dataPath = Path.Combine(Application.persistentDataPath, filename + ".json"); - - Load(); + dataPath = Path.Combine(Application.persistentDataPath, filename + ".csv"); + CheckSaveFileExists(); } /// @@ -33,47 +35,166 @@ public SaveData(string filename) /// Game data from this session public void SaveGameData(T gameData) { - savedGames.Add(gameData); - Save(); + using (StreamWriter sw = new StreamWriter(dataPath, true)) + { + if (!saveFileExists) + { + sw.WriteLine(GameDataToCsv(gameData, true)); + saveFileExists = true; + } + + sw.WriteLine(GameDataToCsv(gameData, false)); + } } /// - /// Get data from the last played game session. + /// Get data from the last complete played game session. /// - public T GetLastGameData() + public T GetLastCompleteGameData() { - return savedGames.LastOrDefault(); + if (!saveFileExists) + { + return null; + } + + IEnumerable lastGameData = GetLastNCompleteGamesData(1); + if (lastGameData.Count() != 1) + { + return null; + } + else + { + return lastGameData.ElementAt(0); + } } /// - /// Get data from the last n played games. + /// Get data from the last n complete played games. /// /// Number of games to retrieve - public IEnumerable GetLastNGamesData(int nGames) + public IEnumerable GetLastNCompleteGamesData(int nGames) { - return savedGames.TakeLast(nGames); + List lastCompleteGames = new List(); + if (!saveFileExists) + { + return lastCompleteGames; + } + + IEnumerable csvLines = File.ReadLines(dataPath); + string header = csvLines.First(); + int lineNo = csvLines.Count() - 1; + + // Start from end of file, and find n complete games + while (lineNo > 0 && lastCompleteGames.Count() < nGames) + { + string line = File.ReadLines(dataPath).ElementAt(lineNo); + + T gameData = CsvToGameData(header, line); + if (gameData.gameCompleted) + { + lastCompleteGames.Add(gameData); + } + lineNo--; + } + + return lastCompleteGames; } - public void Save() + /// + /// Convert csv header / row into a GameData object. + /// + /// Csv header as string (first line of csv file) + /// Csv row as string + /// GameData object with fields populated by row values + private T CsvToGameData(string csvHeader, string csvRow) { - string json = JsonUtility.ToJson(this, true); - File.WriteAllText(dataPath, json); + string[] headerNames = csvHeader.Split(','); + string[] values = csvRow.Split(","); + + T gameData = new(); + for (int i = 0; i < headerNames.Length; i++) + { + FieldInfo field = typeof(T).GetField(headerNames[i]); + field.SetValue(gameData, Convert.ChangeType(values[i], field.FieldType)); + } + + return gameData; } /// - /// Load data about previous games from file (if any). + /// Convert GameData object to a csv string. /// - private void Load() + /// GameData to convert + /// When true, returns a csv header string (names of fields), + /// otherwise returns a csv row string (values of fields) + /// Csv string + private string GameDataToCsv(T gameData, bool headerOnly) { - if (File.Exists(dataPath)) + StringBuilder csvString = new StringBuilder(); + FieldInfo[] fields = GetFields(gameData); + + for (int i = 0; i < fields.Length; i++) { - string json = File.ReadAllText(dataPath); - SaveData loadedData = JsonUtility.FromJson>(json); + if (headerOnly) + { + csvString.Append(fields[i].Name); + } + else + { + csvString.Append(fields[i].GetValue(gameData)); + } + if (i < fields.Length - 1) + { + csvString.Append(","); + } + } + + return csvString.ToString(); + } + + /// + /// Return info on all public fields. + /// Order is: date, startTime, endTime, gameCompleted, then any + /// other fields in alphabetical order. + /// + private FieldInfo[] GetFields(T gameData) + { + Type type = gameData.GetType(); + FieldInfo[] fields = type.GetFields(); + FieldInfo[] sortedFields = new FieldInfo[fields.Length]; + + // We return date, startTime, endTime, gameCompleted first (as this is general data + // for all games, and useful to have at the start of the csv) + sortedFields[0] = type.GetField("date"); + sortedFields[1] = type.GetField("startTime"); + sortedFields[2] = type.GetField("endTime"); + sortedFields[3] = type.GetField("gameCompleted"); + + // Then, all other fields sorted in alphabetical order + Array.Sort(fields, (x, y) => String.Compare(x.Name, y.Name)); - if (loadedData.savedGames != null) + int nextIndex = 4; + foreach (FieldInfo field in fields) + { + if (!sortedFields.Contains(field)) { - this.savedGames = loadedData.savedGames; + sortedFields[nextIndex] = field; + nextIndex++; } } + + return sortedFields; + } + + private void CheckSaveFileExists() + { + if (File.Exists(dataPath)) + { + saveFileExists = true; + } + else + { + saveFileExists = false; + } } } diff --git a/projects/AstroBalance/Assets/Scripts/SpaceWalking/SpaceWalkingManager.cs b/projects/AstroBalance/Assets/Scripts/SpaceWalking/SpaceWalkingManager.cs index 712691ec..b34f5fbd 100644 --- a/projects/AstroBalance/Assets/Scripts/SpaceWalking/SpaceWalkingManager.cs +++ b/projects/AstroBalance/Assets/Scripts/SpaceWalking/SpaceWalkingManager.cs @@ -86,7 +86,9 @@ void Start() private void ChooseGameDifficulty() { SaveData saveData = new(saveFilename); - IEnumerable lastNGamesData = saveData.GetLastNGamesData(nGamesToUpgrade); + IEnumerable lastNGamesData = saveData.GetLastNCompleteGamesData( + nGamesToUpgrade + ); if (debugHeadTurns) { diff --git a/projects/AstroBalance/Assets/Scripts/StarCollector/StarCollectorManager.cs b/projects/AstroBalance/Assets/Scripts/StarCollector/StarCollectorManager.cs index e500ea63..89083f85 100644 --- a/projects/AstroBalance/Assets/Scripts/StarCollector/StarCollectorManager.cs +++ b/projects/AstroBalance/Assets/Scripts/StarCollector/StarCollectorManager.cs @@ -55,7 +55,7 @@ void Start() // Load last game data (if any) from file + choose time limit for this game SaveData saveData = new(saveFilename); - StarCollectorData lastGameData = saveData.GetLastGameData(); + StarCollectorData lastGameData = saveData.GetLastCompleteGameData(); if (lastGameData == null) { diff --git a/projects/AstroBalance/Assets/Scripts/StarMap/StarMapManager.cs b/projects/AstroBalance/Assets/Scripts/StarMap/StarMapManager.cs index 64864364..9b5f7b29 100644 --- a/projects/AstroBalance/Assets/Scripts/StarMap/StarMapManager.cs +++ b/projects/AstroBalance/Assets/Scripts/StarMap/StarMapManager.cs @@ -76,7 +76,7 @@ void Start() private void ChooseConstellationSize() { SaveData saveData = new(saveFilename); - IEnumerable lastNGamesData = saveData.GetLastNGamesData(maxScoreGames); + IEnumerable lastNGamesData = saveData.GetLastNCompleteGamesData(maxScoreGames); int smallConstellationMaxLength = smallConstellation.GetNumberOfStars(); // We haven't played enough games, to get maxScoreGames in a row diff --git a/projects/AstroBalance/Assets/Scripts/StarSeek/StarSeekManager.cs b/projects/AstroBalance/Assets/Scripts/StarSeek/StarSeekManager.cs index 93b95a59..0f7fc49e 100644 --- a/projects/AstroBalance/Assets/Scripts/StarSeek/StarSeekManager.cs +++ b/projects/AstroBalance/Assets/Scripts/StarSeek/StarSeekManager.cs @@ -65,7 +65,9 @@ void Start() private void ChooseGameTimeLimit() { SaveData saveData = new(saveFilename); - IEnumerable lastNGamesData = saveData.GetLastNGamesData(nGamesToUpgrade); + IEnumerable lastNGamesData = saveData.GetLastNCompleteGamesData( + nGamesToUpgrade + ); if (lastNGamesData.Count() < nGamesToUpgrade) {