From 6a013648f427805386b6dc468d4c473564e21174 Mon Sep 17 00:00:00 2001 From: Ellpeck Date: Mon, 1 Jun 2020 14:45:20 +0200 Subject: [PATCH] saving! --- ThemeParkClicker/Attractions/Attraction.cs | 39 ++++------- .../Attractions/AttractionType.cs | 59 +++++++++++++++++ ThemeParkClicker/GameImpl.cs | 52 +++++++++------ ThemeParkClicker/ParkMap.cs | 39 ++++++----- ThemeParkClicker/SaveHandler.cs | 66 +++++++++++++++++++ ThemeParkClicker/ThemeParkClicker.csproj | 1 + ThemeParkClicker/Ui.cs | 10 +-- 7 files changed, 202 insertions(+), 64 deletions(-) create mode 100644 ThemeParkClicker/Attractions/AttractionType.cs create mode 100644 ThemeParkClicker/SaveHandler.cs diff --git a/ThemeParkClicker/Attractions/Attraction.cs b/ThemeParkClicker/Attractions/Attraction.cs index 11530d6..9a0fb16 100644 --- a/ThemeParkClicker/Attractions/Attraction.cs +++ b/ThemeParkClicker/Attractions/Attraction.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Runtime.Serialization; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using MLEM.Extensions; @@ -6,49 +8,36 @@ using MLEM.Startup; using MLEM.Textures; namespace ThemeParkClicker.Attractions { + [DataContract] public class Attraction { - public static readonly Dictionary Attractions = new Dictionary(); public static readonly UniformTextureAtlas Texture = new UniformTextureAtlas(MlemGame.LoadContent("Textures/Attractions"), 16, 16); public static readonly Vector2 TileSize = new Vector2(Texture.RegionWidth, Texture.RegionHeight); - static Attraction() { - Attractions.Add("Carousel", () => new Attraction(new[,] {{true}}, Texture[0, 0, 1, 1], 0.5F, 50)); - Attractions.Add("FoodCourt", () => new Attraction(new[,] {{true, true}}, Texture[1, 0, 2, 1], 1.1F, 300)); - } - - private readonly bool[,] area; - public int Width => this.area.GetLength(1); - public int Height => this.area.GetLength(0); - public readonly TextureRegion TextureRegion; - public readonly float GenerationPerSecond; - public readonly long InitialPrice; + [DataMember] + public readonly AttractionType Type; + [DataMember] private float ticketPercentage; - public Attraction(bool[,] area, TextureRegion texture, float generationPerSecond, long initialPrice) { - this.area = area; - this.TextureRegion = texture; - this.GenerationPerSecond = generationPerSecond; - this.InitialPrice = initialPrice; + public Attraction(AttractionType type) { + this.Type = type; } public IEnumerable GetCoveredTiles() { - for (var x = 0; x < this.Width; x++) { - for (var y = 0; y < this.Height; y++) { - if (this.area[y, x]) + for (var x = 0; x < this.Type.Width; x++) { + for (var y = 0; y < this.Type.Height; y++) { + if (this.Type.Area[y, x]) yield return new Point(x, y); } } } - public delegate Attraction Constructor(); - public long GetPrice() { - return this.InitialPrice; + return this.Type.InitialPrice; } - public void GainTickets(GameTime time) { - this.ticketPercentage += this.GenerationPerSecond * (float) time.ElapsedGameTime.TotalSeconds; + public void Update(GameTime time, TimeSpan passed) { + this.ticketPercentage += this.Type.GenerationPerSecond * (float) passed.TotalSeconds; var amount = this.ticketPercentage.Floor(); if (amount > 0) { GameImpl.Instance.Tickets += amount; diff --git a/ThemeParkClicker/Attractions/AttractionType.cs b/ThemeParkClicker/Attractions/AttractionType.cs new file mode 100644 index 0000000..e6f3d17 --- /dev/null +++ b/ThemeParkClicker/Attractions/AttractionType.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using MLEM.Textures; +using Newtonsoft.Json; + +namespace ThemeParkClicker.Attractions { + [JsonConverter(typeof(Converter))] + public class AttractionType { + + public static readonly Dictionary Attractions = new Dictionary(); + + static AttractionType() { + Register(new AttractionType("Carousel", new[,] {{true}}, Attraction.Texture[0, 0, 1, 1], 0.5F, 50)); + Register(new AttractionType("FoodCourt", new[,] {{true, true}}, Attraction.Texture[1, 0, 2, 1], 1.1F, 300)); + } + + public readonly string Name; + public readonly bool[,] Area; + public int Width => this.Area.GetLength(1); + public int Height => this.Area.GetLength(0); + public readonly TextureRegion TextureRegion; + public readonly float GenerationPerSecond; + public readonly long InitialPrice; + private readonly Constructor create; + + public AttractionType(string name, bool[,] area, TextureRegion texture, float generationPerSecond, long initialPrice, Constructor constructor = null) { + this.Name = name; + this.Area = area; + this.TextureRegion = texture; + this.GenerationPerSecond = generationPerSecond; + this.InitialPrice = initialPrice; + this.create = constructor ?? (t => new Attraction(t)); + } + + public Attraction Create() { + return this.create(this); + } + + private static void Register(AttractionType type) { + Attractions[type.Name] = type; + } + + public delegate Attraction Constructor(AttractionType type); + + public class Converter : JsonConverter { + + public override void WriteJson(JsonWriter writer, AttractionType value, JsonSerializer serializer) { + if (value != null) + writer.WriteValue(value.Name); + } + + public override AttractionType ReadJson(JsonReader reader, Type objectType, AttractionType existingValue, bool hasExistingValue, JsonSerializer serializer) { + return reader.Value != null ? Attractions[reader.Value.ToString()] : null; + } + + } + + } +} \ No newline at end of file diff --git a/ThemeParkClicker/GameImpl.cs b/ThemeParkClicker/GameImpl.cs index acb90e2..9bb236e 100644 --- a/ThemeParkClicker/GameImpl.cs +++ b/ThemeParkClicker/GameImpl.cs @@ -1,3 +1,4 @@ +using System; using System.Numerics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -21,14 +22,15 @@ namespace ThemeParkClicker { this.tickets = value; } } - public ParkMap Map { get; private set; } + public ParkMap Map; public Camera Camera { get; private set; } public Ui Ui { get; private set; } public bool DrawMap; - + public DateTime LastUpdate; public float TicketsPerSecond { get; private set; } private long ticketsPerSecondCounter; private double secondCounter; + private double saveCounter; public GameImpl() { Instance = this; @@ -37,7 +39,6 @@ namespace ThemeParkClicker { protected override void LoadContent() { base.LoadContent(); this.Ui = new Ui(this.UiSystem); - this.Map = new ParkMap(10, 10); this.Camera = new Camera(this.GraphicsDevice) { Scale = 5, AutoScaleWithScreen = true, @@ -46,31 +47,40 @@ namespace ThemeParkClicker { MinScale = 3 }; - #if DEBUG - this.Tickets = 1000; - this.Map.Place(Point.Zero, Attraction.Attractions["Carousel"]()); - this.Map.Place(new Point(1, 0), Attraction.Attractions["Carousel"]()); - this.Map.Place(new Point(3, 0), Attraction.Attractions["Carousel"]()); - this.Map.Place(new Point(1, 2), Attraction.Attractions["FoodCourt"]()); - #endif - } - - public string DisplayTicketCount() { - return this.Tickets.ToString(); + if (!SaveHandler.Load(this)) + this.Map = new ParkMap(10, 10); } protected override void DoUpdate(GameTime gameTime) { base.DoUpdate(gameTime); - this.Map.Update(gameTime); + + var now = DateTime.Now; + if (this.LastUpdate != default) { + var passed = now - this.LastUpdate; + // if more than 1 second passed, the app is minimized or a save was loaded, so we penalize + if (passed.TotalSeconds >= 1) + passed = new TimeSpan(passed.Ticks / 2); + this.Map.Update(gameTime, passed); + } + this.LastUpdate = now; + this.Ui.Update(gameTime); - // we average tickets per second over two seconds + // we average tickets per second over 4 seconds + const float avgTime = 4; this.secondCounter += gameTime.ElapsedGameTime.TotalSeconds; - if (this.secondCounter >= 2) { - this.secondCounter -= 2; - this.TicketsPerSecond = this.ticketsPerSecondCounter / 2F; + if (this.secondCounter >= avgTime) { + this.secondCounter -= avgTime; + this.TicketsPerSecond = this.ticketsPerSecondCounter / avgTime; this.ticketsPerSecondCounter = 0; } + + // save every 3 seconds + this.saveCounter += gameTime.ElapsedGameTime.TotalSeconds; + if (this.saveCounter >= 3) { + this.saveCounter = 0; + SaveHandler.Save(this); + } } protected override void DoDraw(GameTime gameTime) { @@ -83,5 +93,9 @@ namespace ThemeParkClicker { base.DoDraw(gameTime); } + public string DisplayTicketCount() { + return this.Tickets.ToString(); + } + } } \ No newline at end of file diff --git a/ThemeParkClicker/ParkMap.cs b/ThemeParkClicker/ParkMap.cs index 06f83c1..824ac38 100644 --- a/ThemeParkClicker/ParkMap.cs +++ b/ThemeParkClicker/ParkMap.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input.Touch; @@ -11,13 +12,15 @@ using MLEM.Textures; using ThemeParkClicker.Attractions; namespace ThemeParkClicker { + [DataContract] public class ParkMap { + [DataMember] public readonly int Width; + [DataMember] public readonly int Height; - - private readonly Attraction[,] grid; - private readonly Dictionary attractions = new Dictionary(); + [DataMember] + private readonly List<(Point, Attraction)> attractions = new List<(Point, Attraction)>(); public Attraction PlacingAttraction; public Point PlacingPosition; @@ -26,12 +29,11 @@ namespace ThemeParkClicker { public ParkMap(int width, int height) { this.Width = width; this.Height = height; - this.grid = new Attraction[width, height]; } - public void Update(GameTime time) { - foreach (var attraction in this.attractions.Values) - attraction.GainTickets(time); + public void Update(GameTime time, TimeSpan passed) { + foreach (var (_, attraction) in this.attractions) + attraction.Update(time, passed); // map movement if (GameImpl.Instance.DrawMap) { @@ -74,29 +76,36 @@ namespace ThemeParkClicker { // draw ground batch.Draw(batch.GetBlankTexture(), new RectangleF(position, new Vector2(this.Width, this.Height) * tileSize), ColorExtensions.FromHex(0xff53a662) * alpha); // draw attractions - foreach (var kv in this.attractions) - batch.Draw(kv.Value.TextureRegion, position + kv.Key.ToVector2() * tileSize, Color.White * alpha, 0, Vector2.Zero, scale, SpriteEffects.None, 0); + foreach (var (pos, attraction) in this.attractions) + batch.Draw(attraction.Type.TextureRegion, position + pos.ToVector2() * tileSize, Color.White * alpha, 0, Vector2.Zero, scale, SpriteEffects.None, 0); // placing attraction if (this.PlacingAttraction != null) { var placingPos = position + this.PlacingPosition.ToVector2() * tileSize; foreach (var pos in this.PlacingAttraction.GetCoveredTiles()) batch.Draw(batch.GetBlankTexture(), new RectangleF(placingPos + pos.ToVector2() * tileSize, tileSize), Color.Black * 0.15F * alpha); - batch.Draw(this.PlacingAttraction.TextureRegion, placingPos, Color.White * alpha * 0.5F, 0, Vector2.Zero, scale, SpriteEffects.None, 0); + batch.Draw(this.PlacingAttraction.Type.TextureRegion, placingPos, Color.White * alpha * 0.5F, 0, Vector2.Zero, scale, SpriteEffects.None, 0); } } public bool CanPlace(Point position, Attraction attraction) { - foreach (var (x, y) in attraction.GetCoveredTiles()) { - if (this.grid[position.X + x, position.Y + y] != null) + foreach (var offset in attraction.GetCoveredTiles()) { + if (this.GetAttractionAt(position + offset) != null) return false; } return true; } public void Place(Point position, Attraction attraction) { - foreach (var (x, y) in attraction.GetCoveredTiles()) - this.grid[position.X + x, position.Y + y] = attraction; - this.attractions[position] = attraction; + this.attractions.RemoveAll(pa => pa.Item1 == position); + this.attractions.Add((position, attraction)); + } + + public Attraction GetAttractionAt(Point position) { + foreach (var (pos, attraction) in this.attractions) { + if (attraction.GetCoveredTiles().Any(p => pos + p == position)) + return attraction; + } + return null; } } diff --git a/ThemeParkClicker/SaveHandler.cs b/ThemeParkClicker/SaveHandler.cs new file mode 100644 index 0000000..bfb1bbf --- /dev/null +++ b/ThemeParkClicker/SaveHandler.cs @@ -0,0 +1,66 @@ +using System; +using System.IO; +using System.Numerics; +using Newtonsoft.Json; + +namespace ThemeParkClicker { + public static class SaveHandler { + + private static readonly JsonSerializer Serializer = JsonSerializer.Create(new JsonSerializerSettings { + DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, + TypeNameHandling = TypeNameHandling.Auto, + Formatting = Formatting.Indented + }); + private const int SaveVersion = 1; + + public static void Save(GameImpl game) { + var file = GetSaveFile(true); + using (var stream = new JsonTextWriter(file.CreateText())) { + var data = new SaveData { + SaveVersion = SaveVersion, + Tickets = game.Tickets, + LastUpdate = game.LastUpdate, + Map = game.Map + }; + Serializer.Serialize(stream, data); + } + Console.WriteLine("Saved at " + file.FullName); + } + + public static bool Load(GameImpl game) { + var file = GetSaveFile(false); + if (!file.Exists) + return false; + using (var stream = new JsonTextReader(file.OpenText())) { + var data = Serializer.Deserialize(stream); + game.Tickets = data.Tickets; + game.LastUpdate = data.LastUpdate; + game.Map = data.Map; + } + Console.WriteLine("Loaded from " + file.FullName); + return true; + } + + public static DirectoryInfo GetGameDirectory(bool create) { + var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + var dir = new DirectoryInfo(Path.Combine(path, "ThemeParkClicker")); + if (!dir.Exists && create) + dir.Create(); + return dir; + } + + private static FileInfo GetSaveFile(bool create) { + return new FileInfo(Path.Combine(GetGameDirectory(create).FullName, "Save")); + } + + } + + public class SaveData { + + public int SaveVersion; + public BigInteger Tickets; + public DateTime LastUpdate; + public ParkMap Map; + + } +} \ No newline at end of file diff --git a/ThemeParkClicker/ThemeParkClicker.csproj b/ThemeParkClicker/ThemeParkClicker.csproj index 6342d6a..98497eb 100644 --- a/ThemeParkClicker/ThemeParkClicker.csproj +++ b/ThemeParkClicker/ThemeParkClicker.csproj @@ -10,6 +10,7 @@ all + \ No newline at end of file diff --git a/ThemeParkClicker/Ui.cs b/ThemeParkClicker/Ui.cs index 4f2ac05..2634ede 100644 --- a/ThemeParkClicker/Ui.cs +++ b/ThemeParkClicker/Ui.cs @@ -107,8 +107,8 @@ namespace ThemeParkClicker { ChildPadding = new Padding(5, 15, 5, 5), IsHidden = true }; - foreach (var attraction in Attraction.Attractions) { - var instance = attraction.Value(); + foreach (var attraction in AttractionType.Attractions) { + var instance = attraction.Value.Create(); var button = buyUi.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 40)) { ChildPadding = new Vector2(4), Padding = new Padding(0, 0, 0, 4), @@ -116,7 +116,7 @@ namespace ThemeParkClicker { var map = GameImpl.Instance.Map; var pos = new Vector2(map.Width, map.Height) / 2; GameImpl.Instance.Camera.LookingPosition = (pos + new Vector2(0.5F)) * Attraction.TileSize; - map.PlacingAttraction = attraction.Value(); + map.PlacingAttraction = attraction.Value.Create(); map.PlacingPosition = pos.ToPoint(); var yesNoUi = new Group(Anchor.BottomLeft, new Vector2(1)); @@ -142,8 +142,8 @@ namespace ThemeParkClicker { }; var center = button.AddChild(new Group(Anchor.Center, new Vector2(0.8F, 1), false) {CanBeMoused = false}); center.AddChild(new Paragraph(Anchor.AutoCenter, 1, attraction.Key, true)); - center.AddChild(new Paragraph(Anchor.AutoCenter, 1, instance.GenerationPerSecond + "/s", true) {TextScale = 0.08F}); - var image = button.AddChild(new Image(Anchor.CenterLeft, new Vector2(1), instance.TextureRegion) { + center.AddChild(new Paragraph(Anchor.AutoCenter, 1, instance.Type.GenerationPerSecond + "/s", true) {TextScale = 0.08F}); + var image = button.AddChild(new Image(Anchor.CenterLeft, new Vector2(1), instance.Type.TextureRegion) { Padding = new Vector2(4) }); button.OnAreaUpdated += e => image.Size = new Vector2(e.DisplayArea.Height / e.Scale);