|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using static isometricparkfna.CellMap;
|
|
|
using System.Linq;
|
|
|
|
|
|
namespace isometricparkfna
|
|
|
{
|
|
|
public struct Budget
|
|
|
{
|
|
|
public DateTime DateTime;
|
|
|
|
|
|
//assets
|
|
|
public decimal money;
|
|
|
|
|
|
|
|
|
//revenue
|
|
|
public decimal subsidy;
|
|
|
|
|
|
//expenses
|
|
|
public decimal upkeep;
|
|
|
public decimal tree_planting;
|
|
|
public decimal tree_clearing;
|
|
|
|
|
|
|
|
|
public decimal final_money;
|
|
|
public decimal cashflow;
|
|
|
|
|
|
|
|
|
//misc
|
|
|
public int trees;
|
|
|
|
|
|
}
|
|
|
|
|
|
public class Simulation
|
|
|
{
|
|
|
public const int START_YEAR = 2020;
|
|
|
public const int START_MONTH = 1;
|
|
|
public const int START_DAY = 1;
|
|
|
|
|
|
public DateTime START_DATETIME{
|
|
|
get {
|
|
|
return new DateTime(START_YEAR, START_MONTH, START_DAY);
|
|
|
}}
|
|
|
|
|
|
|
|
|
private const float SPONTANEOUS_NEW_TREE_CHANCE = 0.9995f;
|
|
|
private const float NEIGHBOR_NEW_TREE_CHANCE = 0.995f;
|
|
|
private const float NEIGHBOR_CROWDS_TREE_CHANCE = 0.995f;
|
|
|
|
|
|
public const int TREE_PLANT_COST = 750;
|
|
|
public const int TREE_CLEAR_COST = 500;
|
|
|
|
|
|
public const int MAX_TREES_TO_PLANT = 25;
|
|
|
public const int MAX_TREES_TO_CLEAR = 25;
|
|
|
|
|
|
public int Tick
|
|
|
{
|
|
|
get;
|
|
|
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public float Elapsed
|
|
|
{
|
|
|
get;
|
|
|
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public DateTime DateTime
|
|
|
{
|
|
|
get;
|
|
|
|
|
|
private set;
|
|
|
}
|
|
|
|
|
|
public decimal money;
|
|
|
|
|
|
private List<Budget> budgets;
|
|
|
|
|
|
|
|
|
public Budget latestBudget
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
if (this.budgets.Count >= 1) {
|
|
|
return this.budgets[this.budgets.Count - 1];
|
|
|
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
return new Budget { };
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public Budget previousBudget
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
if (this.budgets.Count >= 2) {
|
|
|
return this.budgets[this.budgets.Count - 2];
|
|
|
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
return new Budget { };
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public float millisecondsPerAdvance { get; private set; }
|
|
|
public String Season { get
|
|
|
{
|
|
|
if (MathUtils.Between(this.DateTime.Month, 3, 5))
|
|
|
{
|
|
|
return "Spring";
|
|
|
}
|
|
|
else if (MathUtils.Between(this.DateTime.Month, 6, 8))
|
|
|
{
|
|
|
return "Summer";
|
|
|
}
|
|
|
else if (MathUtils.Between(this.DateTime.Month, 9, 11))
|
|
|
{
|
|
|
return "Fall";
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
return "Winter";
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public CellMap map;
|
|
|
|
|
|
public int ticksPerAdvance;
|
|
|
private float lastAdvance;
|
|
|
public bool paused;
|
|
|
|
|
|
private Random random;
|
|
|
|
|
|
//forest policy params
|
|
|
private int _tree_planting;
|
|
|
public int tree_planting
|
|
|
{
|
|
|
get {
|
|
|
return _tree_planting;
|
|
|
}
|
|
|
set {
|
|
|
_tree_planting = MathUtils.Clamp(value, 0, MAX_TREES_TO_PLANT);
|
|
|
}
|
|
|
}
|
|
|
private int _tree_clearing = 0;
|
|
|
public int tree_clearing
|
|
|
{
|
|
|
get {
|
|
|
return _tree_clearing;
|
|
|
}
|
|
|
set {
|
|
|
_tree_clearing = MathUtils.Clamp(value, 0, MAX_TREES_TO_CLEAR);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public int crowded_trees
|
|
|
{
|
|
|
get {
|
|
|
return this.map.iterate_cells_with_neighbors(7).Where(c => c.hasTree).Count();
|
|
|
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public float healthy_percent
|
|
|
{
|
|
|
get {
|
|
|
return (float)(this.map.tree_count - this.map.iterate_cells_with_neighbors(7).Where(c => c.hasTree).Count()) / this.map.tree_count * 100;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public double average_tree_age
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return this.map.iterate_cells().Where(c => c.hasTree).Select(c => (this.DateTime - c.planted).Days / 365.0).Average();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public double max_tree_age
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
return this.map.iterate_cells().Where(c => c.hasTree).Select(c => (this.DateTime - c.planted).Days / 365.0).Max();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Simulation(int width, int height, float millisecondsPerAdvance)
|
|
|
{
|
|
|
this.random = new Random();
|
|
|
|
|
|
this.DateTime = new DateTime(START_YEAR, START_MONTH, START_DAY);
|
|
|
|
|
|
this.map = new CellMap(width, height);
|
|
|
this.money = 100000;
|
|
|
this.millisecondsPerAdvance = millisecondsPerAdvance;
|
|
|
|
|
|
this.paused = true;
|
|
|
|
|
|
this.budgets = new List<Budget>();
|
|
|
}
|
|
|
|
|
|
private void advanceSimulation()
|
|
|
{
|
|
|
|
|
|
this.DateTime = this.DateTime.AddMonths(1);
|
|
|
|
|
|
|
|
|
foreach (Cell cell in this.map.iterate_cells())
|
|
|
{
|
|
|
if (random.NextDouble() > SPONTANEOUS_NEW_TREE_CHANCE)
|
|
|
{
|
|
|
cell.addTree(this.DateTime);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int new_planted = 0;
|
|
|
foreach (Cell cell in this.map.iterate_cells_with_neighbors(4))
|
|
|
{
|
|
|
if (random.NextDouble() > NEIGHBOR_NEW_TREE_CHANCE)
|
|
|
{
|
|
|
cell.addTree(this.DateTime);
|
|
|
new_planted += 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int crowded_out = 0;
|
|
|
foreach (Cell cell in this.map.iterate_cells_with_neighbors(7))
|
|
|
{
|
|
|
if (random.NextDouble() > NEIGHBOR_CROWDS_TREE_CHANCE)
|
|
|
{
|
|
|
cell.markTreeDead();
|
|
|
crowded_out += 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int trees_to_plant = this.tree_planting;
|
|
|
|
|
|
while (trees_to_plant > 0 && this.map.remaining_tree_capacity > 0) {
|
|
|
int y = random.Next(0, this.map.MapHeight);
|
|
|
int x = random.Next(0, this.map.MapWidth);
|
|
|
Cell chosen_cell = this.map.cells[x][y];
|
|
|
|
|
|
if (!chosen_cell.hasTree) {
|
|
|
chosen_cell.addTree(this.DateTime);
|
|
|
trees_to_plant -= 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
int trees_to_clear = this.tree_clearing;
|
|
|
foreach (Cell cell in this.map.iterate_cells_with_neighbors(7).Where(c => c.hasTree))
|
|
|
{
|
|
|
if (trees_to_clear > 0) {
|
|
|
cell.removeTree();
|
|
|
trees_to_clear -= 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Budget newBudget = new Budget
|
|
|
{
|
|
|
DateTime = this.DateTime,
|
|
|
money = this.money,
|
|
|
trees = this.map.tree_count,
|
|
|
subsidy = 1000,
|
|
|
upkeep = this.map.tree_count * 1,
|
|
|
tree_planting = this.tree_planting * Simulation.TREE_PLANT_COST,
|
|
|
tree_clearing = this.tree_clearing * Simulation.TREE_CLEAR_COST
|
|
|
};
|
|
|
|
|
|
|
|
|
newBudget = this.applyBudget(newBudget); ;
|
|
|
this.budgets.Add(newBudget);
|
|
|
}
|
|
|
|
|
|
public Budget applyBudget(Budget budget)
|
|
|
{
|
|
|
|
|
|
this.money = budget.money
|
|
|
- (budget.upkeep + budget.tree_planting + budget.tree_clearing)
|
|
|
+ (budget.subsidy);
|
|
|
|
|
|
|
|
|
budget.final_money = this.money;
|
|
|
budget.cashflow = budget.final_money - budget.money;
|
|
|
|
|
|
return budget;
|
|
|
}
|
|
|
|
|
|
public void update(TimeSpan deltaTime)
|
|
|
{
|
|
|
if (!this.paused)
|
|
|
{
|
|
|
this.Elapsed += deltaTime.Milliseconds;
|
|
|
|
|
|
int advancesToSimulate = (int)((this.Elapsed - this.lastAdvance) / this.millisecondsPerAdvance);
|
|
|
|
|
|
for (int i = 0; i < advancesToSimulate; i++)
|
|
|
{
|
|
|
this.advanceSimulation();
|
|
|
|
|
|
//Partial frames haven't been simulated so they're not counted as part of
|
|
|
//lastAdvance
|
|
|
//Example:
|
|
|
//We start at t=100 and simulate every 10 t. If we miss enough updates that
|
|
|
//it's t=125, we have 2.5 steps to simulate. However, we only want to simulate
|
|
|
//whole steps for simplicity's sake, so that means we'll simulate 2. But that means we've only simulated
|
|
|
//through t=120, so that's what we want to track in lastAdvance.
|
|
|
this.lastAdvance += advancesToSimulate * this.millisecondsPerAdvance;
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
if ((this.Tick % this.millisecondsPerAdvance) == 0)
|
|
|
{
|
|
|
this.DateTime = this.DateTime.AddMonths(1);
|
|
|
}*/
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|