This commit is contained in:
Ellpeck 2020-06-01 14:45:20 +02:00
parent 7c51c923b9
commit 6a013648f4
7 changed files with 202 additions and 64 deletions

View file

@ -1,4 +1,6 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.Serialization;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using MLEM.Extensions; using MLEM.Extensions;
@ -6,49 +8,36 @@ using MLEM.Startup;
using MLEM.Textures; using MLEM.Textures;
namespace ThemeParkClicker.Attractions { namespace ThemeParkClicker.Attractions {
[DataContract]
public class Attraction { public class Attraction {
public static readonly Dictionary<string, Constructor> Attractions = new Dictionary<string, Constructor>();
public static readonly UniformTextureAtlas Texture = new UniformTextureAtlas(MlemGame.LoadContent<Texture2D>("Textures/Attractions"), 16, 16); public static readonly UniformTextureAtlas Texture = new UniformTextureAtlas(MlemGame.LoadContent<Texture2D>("Textures/Attractions"), 16, 16);
public static readonly Vector2 TileSize = new Vector2(Texture.RegionWidth, Texture.RegionHeight); public static readonly Vector2 TileSize = new Vector2(Texture.RegionWidth, Texture.RegionHeight);
static Attraction() { [DataMember]
Attractions.Add("Carousel", () => new Attraction(new[,] {{true}}, Texture[0, 0, 1, 1], 0.5F, 50)); public readonly AttractionType Type;
Attractions.Add("FoodCourt", () => new Attraction(new[,] {{true, true}}, Texture[1, 0, 2, 1], 1.1F, 300)); [DataMember]
}
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;
private float ticketPercentage; private float ticketPercentage;
public Attraction(bool[,] area, TextureRegion texture, float generationPerSecond, long initialPrice) { public Attraction(AttractionType type) {
this.area = area; this.Type = type;
this.TextureRegion = texture;
this.GenerationPerSecond = generationPerSecond;
this.InitialPrice = initialPrice;
} }
public IEnumerable<Point> GetCoveredTiles() { public IEnumerable<Point> GetCoveredTiles() {
for (var x = 0; x < this.Width; x++) { for (var x = 0; x < this.Type.Width; x++) {
for (var y = 0; y < this.Height; y++) { for (var y = 0; y < this.Type.Height; y++) {
if (this.area[y, x]) if (this.Type.Area[y, x])
yield return new Point(x, y); yield return new Point(x, y);
} }
} }
} }
public delegate Attraction Constructor();
public long GetPrice() { public long GetPrice() {
return this.InitialPrice; return this.Type.InitialPrice;
} }
public void GainTickets(GameTime time) { public void Update(GameTime time, TimeSpan passed) {
this.ticketPercentage += this.GenerationPerSecond * (float) time.ElapsedGameTime.TotalSeconds; this.ticketPercentage += this.Type.GenerationPerSecond * (float) passed.TotalSeconds;
var amount = this.ticketPercentage.Floor(); var amount = this.ticketPercentage.Floor();
if (amount > 0) { if (amount > 0) {
GameImpl.Instance.Tickets += amount; GameImpl.Instance.Tickets += amount;

View file

@ -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<string, AttractionType> Attractions = new Dictionary<string, AttractionType>();
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<AttractionType> {
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;
}
}
}
}

View file

@ -1,3 +1,4 @@
using System;
using System.Numerics; using System.Numerics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@ -21,14 +22,15 @@ namespace ThemeParkClicker {
this.tickets = value; this.tickets = value;
} }
} }
public ParkMap Map { get; private set; } public ParkMap Map;
public Camera Camera { get; private set; } public Camera Camera { get; private set; }
public Ui Ui { get; private set; } public Ui Ui { get; private set; }
public bool DrawMap; public bool DrawMap;
public DateTime LastUpdate;
public float TicketsPerSecond { get; private set; } public float TicketsPerSecond { get; private set; }
private long ticketsPerSecondCounter; private long ticketsPerSecondCounter;
private double secondCounter; private double secondCounter;
private double saveCounter;
public GameImpl() { public GameImpl() {
Instance = this; Instance = this;
@ -37,7 +39,6 @@ namespace ThemeParkClicker {
protected override void LoadContent() { protected override void LoadContent() {
base.LoadContent(); base.LoadContent();
this.Ui = new Ui(this.UiSystem); this.Ui = new Ui(this.UiSystem);
this.Map = new ParkMap(10, 10);
this.Camera = new Camera(this.GraphicsDevice) { this.Camera = new Camera(this.GraphicsDevice) {
Scale = 5, Scale = 5,
AutoScaleWithScreen = true, AutoScaleWithScreen = true,
@ -46,31 +47,40 @@ namespace ThemeParkClicker {
MinScale = 3 MinScale = 3
}; };
#if DEBUG if (!SaveHandler.Load(this))
this.Tickets = 1000; this.Map = new ParkMap(10, 10);
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();
} }
protected override void DoUpdate(GameTime gameTime) { protected override void DoUpdate(GameTime gameTime) {
base.DoUpdate(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); 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; this.secondCounter += gameTime.ElapsedGameTime.TotalSeconds;
if (this.secondCounter >= 2) { if (this.secondCounter >= avgTime) {
this.secondCounter -= 2; this.secondCounter -= avgTime;
this.TicketsPerSecond = this.ticketsPerSecondCounter / 2F; this.TicketsPerSecond = this.ticketsPerSecondCounter / avgTime;
this.ticketsPerSecondCounter = 0; 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) { protected override void DoDraw(GameTime gameTime) {
@ -83,5 +93,9 @@ namespace ThemeParkClicker {
base.DoDraw(gameTime); base.DoDraw(gameTime);
} }
public string DisplayTicketCount() {
return this.Tickets.ToString();
}
} }
} }

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.Serialization;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input.Touch; using Microsoft.Xna.Framework.Input.Touch;
@ -11,13 +12,15 @@ using MLEM.Textures;
using ThemeParkClicker.Attractions; using ThemeParkClicker.Attractions;
namespace ThemeParkClicker { namespace ThemeParkClicker {
[DataContract]
public class ParkMap { public class ParkMap {
[DataMember]
public readonly int Width; public readonly int Width;
[DataMember]
public readonly int Height; public readonly int Height;
[DataMember]
private readonly Attraction[,] grid; private readonly List<(Point, Attraction)> attractions = new List<(Point, Attraction)>();
private readonly Dictionary<Point, Attraction> attractions = new Dictionary<Point, Attraction>();
public Attraction PlacingAttraction; public Attraction PlacingAttraction;
public Point PlacingPosition; public Point PlacingPosition;
@ -26,12 +29,11 @@ namespace ThemeParkClicker {
public ParkMap(int width, int height) { public ParkMap(int width, int height) {
this.Width = width; this.Width = width;
this.Height = height; this.Height = height;
this.grid = new Attraction[width, height];
} }
public void Update(GameTime time) { public void Update(GameTime time, TimeSpan passed) {
foreach (var attraction in this.attractions.Values) foreach (var (_, attraction) in this.attractions)
attraction.GainTickets(time); attraction.Update(time, passed);
// map movement // map movement
if (GameImpl.Instance.DrawMap) { if (GameImpl.Instance.DrawMap) {
@ -74,29 +76,36 @@ namespace ThemeParkClicker {
// draw ground // draw ground
batch.Draw(batch.GetBlankTexture(), new RectangleF(position, new Vector2(this.Width, this.Height) * tileSize), ColorExtensions.FromHex(0xff53a662) * alpha); batch.Draw(batch.GetBlankTexture(), new RectangleF(position, new Vector2(this.Width, this.Height) * tileSize), ColorExtensions.FromHex(0xff53a662) * alpha);
// draw attractions // draw attractions
foreach (var kv in this.attractions) foreach (var (pos, attraction) in this.attractions)
batch.Draw(kv.Value.TextureRegion, position + kv.Key.ToVector2() * tileSize, Color.White * alpha, 0, Vector2.Zero, scale, SpriteEffects.None, 0); batch.Draw(attraction.Type.TextureRegion, position + pos.ToVector2() * tileSize, Color.White * alpha, 0, Vector2.Zero, scale, SpriteEffects.None, 0);
// placing attraction // placing attraction
if (this.PlacingAttraction != null) { if (this.PlacingAttraction != null) {
var placingPos = position + this.PlacingPosition.ToVector2() * tileSize; var placingPos = position + this.PlacingPosition.ToVector2() * tileSize;
foreach (var pos in this.PlacingAttraction.GetCoveredTiles()) 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(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) { public bool CanPlace(Point position, Attraction attraction) {
foreach (var (x, y) in attraction.GetCoveredTiles()) { foreach (var offset in attraction.GetCoveredTiles()) {
if (this.grid[position.X + x, position.Y + y] != null) if (this.GetAttractionAt(position + offset) != null)
return false; return false;
} }
return true; return true;
} }
public void Place(Point position, Attraction attraction) { public void Place(Point position, Attraction attraction) {
foreach (var (x, y) in attraction.GetCoveredTiles()) this.attractions.RemoveAll(pa => pa.Item1 == position);
this.grid[position.X + x, position.Y + y] = attraction; this.attractions.Add((position, attraction));
this.attractions[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;
} }
} }

View file

@ -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<SaveData>(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;
}
}

View file

@ -10,6 +10,7 @@
<PackageReference Include="MonoGame.Framework.Portable" Version="3.7.1.189"> <PackageReference Include="MonoGame.Framework.Portable" Version="3.7.1.189">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -107,8 +107,8 @@ namespace ThemeParkClicker {
ChildPadding = new Padding(5, 15, 5, 5), ChildPadding = new Padding(5, 15, 5, 5),
IsHidden = true IsHidden = true
}; };
foreach (var attraction in Attraction.Attractions) { foreach (var attraction in AttractionType.Attractions) {
var instance = attraction.Value(); var instance = attraction.Value.Create();
var button = buyUi.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 40)) { var button = buyUi.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 40)) {
ChildPadding = new Vector2(4), ChildPadding = new Vector2(4),
Padding = new Padding(0, 0, 0, 4), Padding = new Padding(0, 0, 0, 4),
@ -116,7 +116,7 @@ namespace ThemeParkClicker {
var map = GameImpl.Instance.Map; var map = GameImpl.Instance.Map;
var pos = new Vector2(map.Width, map.Height) / 2; var pos = new Vector2(map.Width, map.Height) / 2;
GameImpl.Instance.Camera.LookingPosition = (pos + new Vector2(0.5F)) * Attraction.TileSize; GameImpl.Instance.Camera.LookingPosition = (pos + new Vector2(0.5F)) * Attraction.TileSize;
map.PlacingAttraction = attraction.Value(); map.PlacingAttraction = attraction.Value.Create();
map.PlacingPosition = pos.ToPoint(); map.PlacingPosition = pos.ToPoint();
var yesNoUi = new Group(Anchor.BottomLeft, new Vector2(1)); 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}); 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, attraction.Key, true));
center.AddChild(new Paragraph(Anchor.AutoCenter, 1, instance.GenerationPerSecond + "<i ticket>/s", true) {TextScale = 0.08F}); center.AddChild(new Paragraph(Anchor.AutoCenter, 1, instance.Type.GenerationPerSecond + "<i ticket>/s", true) {TextScale = 0.08F});
var image = button.AddChild(new Image(Anchor.CenterLeft, new Vector2(1), instance.TextureRegion) { var image = button.AddChild(new Image(Anchor.CenterLeft, new Vector2(1), instance.Type.TextureRegion) {
Padding = new Vector2(4) Padding = new Vector2(4)
}); });
button.OnAreaUpdated += e => image.Size = new Vector2(e.DisplayArea.Height / e.Scale); button.OnAreaUpdated += e => image.Size = new Vector2(e.DisplayArea.Height / e.Scale);