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.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<string, Constructor> Attractions = new Dictionary<string, Constructor>();
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);
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<Point> 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;

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

View file

@ -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<Point, Attraction> attractions = new Dictionary<Point, Attraction>();
[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;
}
}

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">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
</Project>

View file

@ -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 + "<i ticket>/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 + "<i ticket>/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);