diff --git a/TouchyTickets/Attractions/AttractionType.cs b/TouchyTickets/Attractions/AttractionType.cs index 0d56fcb..642c378 100644 --- a/TouchyTickets/Attractions/AttractionType.cs +++ b/TouchyTickets/Attractions/AttractionType.cs @@ -38,7 +38,7 @@ namespace TouchyTickets.Attractions { } private static void Register(AttractionType type) { - Attractions[type.Name] = type; + Attractions.Add(type.Name, type); } public delegate Attraction Constructor(AttractionType type); diff --git a/TouchyTickets/Content/Textures/Ui.aseprite b/TouchyTickets/Content/Textures/Ui.aseprite index 9038f76..e838644 100644 Binary files a/TouchyTickets/Content/Textures/Ui.aseprite and b/TouchyTickets/Content/Textures/Ui.aseprite differ diff --git a/TouchyTickets/Content/Textures/Ui.png b/TouchyTickets/Content/Textures/Ui.png index 529d563..5c4e6eb 100644 Binary files a/TouchyTickets/Content/Textures/Ui.png and b/TouchyTickets/Content/Textures/Ui.png differ diff --git a/TouchyTickets/GameImpl.cs b/TouchyTickets/GameImpl.cs index 3f3d11e..5a80ebd 100644 --- a/TouchyTickets/GameImpl.cs +++ b/TouchyTickets/GameImpl.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Numerics; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -7,12 +8,16 @@ using MLEM.Extensions; using MLEM.Font; using MLEM.Startup; using TouchyTickets.Attractions; +using TouchyTickets.Upgrades; namespace TouchyTickets { public class GameImpl : MlemGame { public static GameImpl Instance { get; private set; } + public readonly ISet AppliedUpgrades = new HashSet(); public BigInteger Tickets; + public int TimesRestarted; + public int Stars; public ParkMap Map; public Camera Camera { get; private set; } public Ui Ui { get; private set; } @@ -28,7 +33,7 @@ namespace TouchyTickets { base.LoadContent(); if (!SaveHandler.Load(this)) - this.Map = new ParkMap(10, 10); + this.Map = new ParkMap(12, 12); this.Ui = new Ui(this.UiSystem); this.Camera = new Camera(this.GraphicsDevice) { @@ -36,7 +41,7 @@ namespace TouchyTickets { AutoScaleWithScreen = true, AutoScaleReferenceSize = new Point(720, 1280), MaxScale = 24, - MinScale = 3 + MinScale = 2 }; } @@ -73,22 +78,8 @@ namespace TouchyTickets { base.DoDraw(gameTime); } - public string DisplayTicketCount() { - if (this.Tickets < 1000) - return this.Tickets.ToString(); - // thousands - if (this.Tickets < 1000000) - return this.Tickets.ToString("0,.##K"); - // millions - if (this.Tickets < 1000000000) - return this.Tickets.ToString("0,,.##M"); - // billions - if (this.Tickets < 1000000000000) - return this.Tickets.ToString("0,,,.##B"); - // trillions - if (this.Tickets < 1000000000000000) - return this.Tickets.ToString("0,,,,.##T"); - return this.Tickets.ToString("0,,,,,.##Q"); + public BigInteger GetStarPrice() { + return BigInteger.Pow(1000000, this.TimesRestarted / 2 + 1); } } diff --git a/TouchyTickets/ParkMap.cs b/TouchyTickets/ParkMap.cs index 2a2934d..659d442 100644 --- a/TouchyTickets/ParkMap.cs +++ b/TouchyTickets/ParkMap.cs @@ -16,7 +16,7 @@ namespace TouchyTickets { public class ParkMap { private static readonly UniformTextureAtlas TilesTexture = new UniformTextureAtlas(MlemGame.LoadContent("Textures/Tiles"), 16, 16); - private const int AdditionalRadius = 10; + private const int AdditionalRadius = 15; [DataMember] public readonly int Width; [DataMember] @@ -37,12 +37,15 @@ namespace TouchyTickets { // set up trees var random = new Random(); - for (var i = 0; i < 100; i++) { - var type = random.Next(3); - var x = random.Next(-AdditionalRadius, this.Width + AdditionalRadius); - var y = random.Next(-AdditionalRadius, this.Height + AdditionalRadius); - if (x < 0 || y < 0 || x >= this.Width || y >= this.Width) + for (var x = -AdditionalRadius; x < this.Width + AdditionalRadius; x++) { + for (var y = -AdditionalRadius; y < this.Height + AdditionalRadius; y++) { + if (x >= 0 && y >= 0 && x < this.Width && y < this.Height) + continue; + if (random.Next(15) != 0) + continue; + var type = random.Next(3); this.treePositions[new Point(x, y)] = type; + } } // set up fences @@ -163,5 +166,10 @@ namespace TouchyTickets { return this.attractions.Count(a => a.Item2.Type == type); } + public void CopyAttractionsFrom(ParkMap other) { + foreach (var (pos, attraction) in other.attractions) + this.Place(pos, attraction); + } + } } \ No newline at end of file diff --git a/TouchyTickets/SaveHandler.cs b/TouchyTickets/SaveHandler.cs index f3e2c17..3e3a633 100644 --- a/TouchyTickets/SaveHandler.cs +++ b/TouchyTickets/SaveHandler.cs @@ -1,7 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; +using System.Linq; using System.Numerics; using Newtonsoft.Json; +using TouchyTickets.Upgrades; namespace TouchyTickets { public static class SaveHandler { @@ -20,7 +23,10 @@ namespace TouchyTickets { SaveVersion = SaveVersion, Tickets = game.Tickets, LastUpdate = game.LastUpdate, - Map = game.Map + Map = game.Map, + Stars = game.Stars, + TimesRestarted = game.TimesRestarted, + Upgrades = game.AppliedUpgrades.Select(u => u.Name).ToList() }; Serializer.Serialize(stream, data); } @@ -35,6 +41,11 @@ namespace TouchyTickets { game.Tickets = data.Tickets; game.LastUpdate = data.LastUpdate; game.Map = data.Map; + game.Stars = data.Stars; + game.TimesRestarted = data.TimesRestarted; + game.AppliedUpgrades.Clear(); + foreach (var name in data.Upgrades) + game.AppliedUpgrades.Add(Upgrade.Upgrades[name]); } return true; } @@ -59,6 +70,9 @@ namespace TouchyTickets { public BigInteger Tickets; public DateTime LastUpdate; public ParkMap Map; + public int Stars; + public int TimesRestarted; + public List Upgrades; } } \ No newline at end of file diff --git a/TouchyTickets/TouchyTickets.csproj b/TouchyTickets/TouchyTickets.csproj index 00997ad..6a59fe0 100644 --- a/TouchyTickets/TouchyTickets.csproj +++ b/TouchyTickets/TouchyTickets.csproj @@ -6,7 +6,7 @@ - + all diff --git a/TouchyTickets/Ui.cs b/TouchyTickets/Ui.cs index 0dcbf48..6ea83e3 100644 --- a/TouchyTickets/Ui.cs +++ b/TouchyTickets/Ui.cs @@ -16,6 +16,7 @@ using MLEM.Textures; using MLEM.Ui; using MLEM.Ui.Elements; using TouchyTickets.Attractions; +using TouchyTickets.Upgrades; namespace TouchyTickets { public class Ui { @@ -36,6 +37,7 @@ namespace TouchyTickets { this.uiSystem.Style.Font = new GenericSpriteFont(MlemGame.LoadContent("Fonts/Regular")); this.uiSystem.Style.TextScale = 0.1F; this.uiSystem.TextFormatter.AddImage("ticket", Texture[2, 0]); + this.uiSystem.TextFormatter.AddImage("star", Texture[3, 0]); // main ticket store ui var rainingTickets = new List(); @@ -55,7 +57,7 @@ namespace TouchyTickets { } }; var ticketGroup = main.AddChild(new Group(Anchor.AutoCenter, new Vector2(1, 0.15F), false)); - ticketGroup.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => GameImpl.Instance.DisplayTicketCount(), true) { + ticketGroup.AddChild(new Paragraph(Anchor.AutoCenter, 10000, p => PrettyPrintNumber(GameImpl.Instance.Tickets) + "", true) { TextScale = 0.3F }); ticketGroup.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => GameImpl.Instance.Map.TicketsPerSecond.ToString("0.##") + "/s", true) { @@ -71,7 +73,13 @@ namespace TouchyTickets { } }); storeGroup.AddChild(new Image(Anchor.TopLeft, Vector2.One, Texture[0, 0, 2, 3]) { - OnPressed = e => GameImpl.Instance.Tickets++, + OnPressed = e => { + #if DEBUG + GameImpl.Instance.Tickets += 500000; + #else + GameImpl.Instance.Tickets++; + #endif + }, CanBeSelected = true, CanBeMoused = true }); @@ -86,9 +94,6 @@ namespace TouchyTickets { map.Draw(time, batch, pos, scale, alpha, false, new RectangleF(Vector2.Zero, mapSize * scale)); }, OnPressed = e => { - var map = GameImpl.Instance.Map; - GameImpl.Instance.Camera.LookingPosition = new Vector2(map.Width, map.Height) / 2 * Attraction.TileSize; - var backUi = new Group(Anchor.BottomLeft, new Vector2(1)); backUi.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(1, 40), "Back") { OnPressed = e2 => this.FadeUi(false, () => this.uiSystem.Remove(e2.Root.Name)) @@ -111,13 +116,12 @@ namespace TouchyTickets { BigInteger price = 0; var button = buyUi.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 40)) { ChildPadding = new Vector2(4), - Padding = new Padding(0, 0, 0, 4), OnPressed = e => { 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.Create(); - map.PlacingPosition = pos.ToPoint(); + // set placing position to center of camera's view + var (posX, posY) = (GameImpl.Instance.Camera.LookingPosition / Attraction.TileSize).ToPoint(); + map.PlacingPosition = new Point(MathHelper.Clamp(posX, 0, map.Width), MathHelper.Clamp(posY, 0, map.Height)); var yesNoUi = new Group(Anchor.BottomLeft, new Vector2(1)); yesNoUi.AddChild(new Button(Anchor.AutoInlineIgnoreOverflow, new Vector2(0.5F, 40), "Back") { @@ -157,7 +161,56 @@ namespace TouchyTickets { } this.uiSystem.Add("Buy", buyUi); - this.swipeRelations = new Element[] {main, buyUi}; + // upgrade ui + var upgradeUi = new Group(Anchor.TopLeft, Vector2.One, false) { + IsHidden = true, + OnDrawn = (e, time, batch, alpha) => batch.Draw(batch.GetBlankTexture(), e.DisplayArea, ColorExtensions.FromHex(0xff86bccf) * alpha) + }; + var upgradeHeader = upgradeUi.AddChild(new Group(Anchor.AutoLeft, new Vector2(1, 0.25F), false)); + upgradeHeader.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => GameImpl.Instance.Stars + "", true) {TextScale = 0.3F}); + upgradeHeader.AddChild(new Button(Anchor.AutoCenter, new Vector2(0.8F, 20), "Earn ") { + PositionOffset = new Vector2(0, 4), + OnUpdated = (e, time) => ((Button) e).IsDisabled = GameImpl.Instance.Tickets < GameImpl.Instance.GetStarPrice(), + OnPressed = e => { + var game = GameImpl.Instance; + game.TimesRestarted++; + game.Stars++; + game.Map = new ParkMap(game.Map.Width, game.Map.Height); + game.Tickets = 0; + } + }); + upgradeHeader.AddChild(new Paragraph(Anchor.AutoCenter, 1, p => $"Requires {PrettyPrintNumber(GameImpl.Instance.GetStarPrice())}", true) { + PositionOffset = new Vector2(0, 2) + }); + var upgradeList = upgradeUi.AddChild(new Panel(Anchor.AutoLeft, new Vector2(1, 0.75F), Vector2.Zero, false, true, new Point(10, 30), false) { + ChildPadding = new Padding(5, 15, 5, 5) + }); + foreach (var upgrade in Upgrade.Upgrades.Values) { + var button = upgradeList.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 40)) { + ChildPadding = new Vector2(4), + OnPressed = e => { + GameImpl.Instance.Stars--; + GameImpl.Instance.AppliedUpgrades.Add(upgrade); + upgrade.OnApplied(); + } + }); + button.OnUpdated += (e, time) => { + // we want to hide if we're active, or if we still need a dependency to be active + button.IsHidden = Upgrade.IsActive(upgrade) || upgrade.Dependencies.Any(u => !Upgrade.IsActive(u)); + button.IsDisabled = GameImpl.Instance.Stars < upgrade.Price; + }; + var center = button.AddChild(new Group(Anchor.Center, new Vector2(0.8F, 1), false) {CanBeMoused = false}); + center.AddChild(new Paragraph(Anchor.AutoCenter, 1, upgrade.Name, true)); + //center.AddChild(new Paragraph(Anchor.AutoCenter, 1, upgrade.Description, true) {TextScale = 0.08F}); + var image = button.AddChild(new Image(Anchor.CenterLeft, new Vector2(1), upgrade.Texture) { + Padding = new Vector2(4) + }); + button.OnAreaUpdated += e => image.Size = new Vector2(e.DisplayArea.Height / e.Scale); + button.AddChild(new Paragraph(Anchor.CenterRight, 1, p => upgrade.Price + "", true)); + } + this.uiSystem.Add("Upgrade", upgradeUi); + + this.swipeRelations = new Element[] {upgradeUi, main, buyUi}; } public void Update(GameTime time) { @@ -247,5 +300,23 @@ namespace TouchyTickets { element.Transform = Matrix.Identity; } + private static string PrettyPrintNumber(BigInteger number) { + if (number < 1000) + return number.ToString(); + // thousands + if (number < 1000000) + return number.ToString("0,.##K"); + // millions + if (number < 1000000000) + return number.ToString("0,,.##M"); + // billions + if (number < 1000000000000) + return number.ToString("0,,,.##B"); + // trillions + if (number < 1000000000000000) + return number.ToString("0,,,,.##T"); + return number.ToString("0,,,,,.##Q"); + } + } } \ No newline at end of file diff --git a/TouchyTickets/Upgrade.cs b/TouchyTickets/Upgrade.cs new file mode 100644 index 0000000..e005c17 --- /dev/null +++ b/TouchyTickets/Upgrade.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Xna.Framework; +using MLEM.Extensions; +using MLEM.Textures; + +namespace TouchyTickets.Upgrades { + public class Upgrade { + + public static readonly Dictionary Upgrades = new Dictionary(); + public static readonly Upgrade[] MapSize = RegisterTiers("MapSize", 10, 1, 0.5F, Ui.Texture[0, 3]); + + public readonly string Name; + public readonly int Price; + public readonly TextureRegion Texture; + public readonly Upgrade[] Dependencies; + + public Upgrade(string name, int price, TextureRegion texture, params Upgrade[] dependencies) { + this.Name = name; + this.Price = price; + this.Texture = texture; + this.Dependencies = dependencies; + } + + private static Upgrade[] RegisterTiers(string name, int amount, int startPrice, float priceIncrease, TextureRegion texture) { + var ret = new Upgrade[amount]; + for (var i = 0; i < amount; i++) { + // every tier except first depends on last tier + var deps = i == 0 ? Array.Empty() : new[] {ret[i - 1]}; + var price = (startPrice * (1 + i * priceIncrease)).Floor(); + Register(ret[i] = new Upgrade(name + (i + 1), price, texture, deps)); + } + return ret; + } + + public void OnApplied() { + // map size upgrades + if (MapSize.Contains(this)) { + var oldMap = GameImpl.Instance.Map; + var newMap = new ParkMap(oldMap.Width + 5, oldMap.Height + 5); + newMap.CopyAttractionsFrom(oldMap); + GameImpl.Instance.Map = newMap; + } + } + + private static void Register(Upgrade upgrade) { + Upgrades.Add(upgrade.Name, upgrade); + } + + public static bool IsActive(Upgrade upgrade) { + return GameImpl.Instance.AppliedUpgrades.Contains(upgrade); + } + + } +} \ No newline at end of file