diff --git a/StackEgg.sln b/StackEgg.sln
new file mode 100644
index 0000000..a1138c6
--- /dev/null
+++ b/StackEgg.sln
@@ -0,0 +1,22 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.30723.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackEgg", "StackEgg\StackEgg.csproj", "{DA42D659-D5F3-47D8-958E-0555A499B40A}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {DA42D659-D5F3-47D8-958E-0555A499B40A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DA42D659-D5F3-47D8-958E-0555A499B40A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DA42D659-D5F3-47D8-958E-0555A499B40A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DA42D659-D5F3-47D8-958E-0555A499B40A}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/StackEgg/App.config b/StackEgg/App.config
new file mode 100644
index 0000000..fad249e
--- /dev/null
+++ b/StackEgg/App.config
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/StackEgg/Program.cs b/StackEgg/Program.cs
new file mode 100644
index 0000000..0f8b765
--- /dev/null
+++ b/StackEgg/Program.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace StackEgg
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ var game = new StackEggGame().Initialize();
+
+ while (true)
+ {
+ if (game.CurrentPhase == null)
+ {
+ switch (game.CurrentPhaseId)
+ {
+ case StackEggPhase.Id.WonTheInternet:
+ Console.WriteLine("Congratulations, your site has won the internet!");
+ break;
+ case StackEggPhase.Id.Failed:
+ Console.WriteLine("Oops, your site was shut down.");
+ break;
+ }
+ return;
+ }
+
+ var sb = new StringBuilder();
+ sb.AppendFormat("{0} day{1} elapsed, your Site is ", game.DaysElapsed, game.DaysElapsed == 1 ? "" : "s");
+ switch (game.CurrentPhaseId)
+ {
+ case StackEggPhase.Id.PrivateBeta:
+ sb.Append("in private beta");
+ break;
+ case StackEggPhase.Id.PublicBeta:
+ sb.Append("in public beta");
+ break;
+ case StackEggPhase.Id.Launched:
+ sb.Append("fully graduated");
+ break;
+ }
+ sb.AppendFormat(", phase progress: {0}%", (int)(game.GetPhaseProgress() * 100));
+ sb.AppendLine();
+ sb.AppendLine("Site stats:");
+ foreach (var stat in game.CurrentPhase.AvailableStats)
+ {
+ var exact = game.Stats[stat];
+ var hearts = StackEggGame.HeartRound(exact);
+ sb.AppendFormat(" {0, -10} {1}{2}", stat, new String('#', hearts), new String('.', 4 - hearts));
+ if (StackEggGame.HeartRoundExtended(exact) < 0)
+ sb.Append(" (dangerously low!)");
+ sb.AppendLine();
+ }
+ if (game.ModeratorFlags > 0)
+ {
+ sb.AppendLine();
+ sb.AppendLine(" Mod flags " + game.ModeratorFlags);
+ }
+
+ sb.AppendLine();
+ if (game.IsFailing)
+ {
+ sb.AppendLine("Your Site is on the verge of being shut down!");
+ sb.AppendLine("Only flagging for moderator attention can save it now.");
+ sb.AppendLine();
+ }
+
+ sb.AppendLine("Choose your action:");
+
+ foreach (var action in game.AvailableActions())
+ {
+ sb.AppendFormat("{0}: {1}", (int)action, action);
+ sb.AppendLine();
+ }
+ sb.AppendLine("q: Quit");
+
+ Console.Write(sb.ToString());
+
+ var validAction = false;
+ StackEggAction chosenAction = StackEggAction.Nothing;
+
+ while (!validAction)
+ {
+ var s = Console.ReadLine();
+ if (s == "q" || s == "Q")
+ {
+ Console.WriteLine("Good bye.");
+ return;
+ }
+ else
+ {
+ int num;
+ if (int.TryParse(s, out num))
+ {
+ chosenAction = (StackEggAction)num;
+ if (game.ActionIsLegal(chosenAction))
+ validAction = true;
+ }
+ }
+ if (!validAction)
+ Console.WriteLine("That's not a valid action, choose again:");
+ }
+ Console.WriteLine();
+ Console.WriteLine();
+ game.AdvanceDay(chosenAction);
+ }
+ }
+ }
+}
diff --git a/StackEgg/Properties/AssemblyInfo.cs b/StackEgg/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..0bc9eea
--- /dev/null
+++ b/StackEgg/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("StackEgg")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Stack Exchange Inc.")]
+[assembly: AssemblyProduct("StackEgg")]
+[assembly: AssemblyCopyright("Copyright © Stack Exchange Inc. 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("1ac6139c-7415-4295-8d9d-488b7db7e0d8")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/StackEgg/StackEgg.csproj b/StackEgg/StackEgg.csproj
new file mode 100644
index 0000000..dfca08c
--- /dev/null
+++ b/StackEgg/StackEgg.csproj
@@ -0,0 +1,63 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {DA42D659-D5F3-47D8-958E-0555A499B40A}
+ Exe
+ Properties
+ StackEgg
+ StackEgg
+ v4.5
+ 512
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/StackEgg/StackEggAction.cs b/StackEgg/StackEggAction.cs
new file mode 100644
index 0000000..b3e71aa
--- /dev/null
+++ b/StackEgg/StackEggAction.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace StackEgg
+{
+ public enum StackEggAction
+ {
+ Nothing = 0,
+
+ // the follwing five are the "base actions"
+ Ask,
+ Answer,
+ Upvote,
+ Downvote,
+ Close,
+
+ FlagForModerator
+ }
+}
\ No newline at end of file
diff --git a/StackEgg/StackEggGame.cs b/StackEgg/StackEggGame.cs
new file mode 100644
index 0000000..9f30497
--- /dev/null
+++ b/StackEgg/StackEggGame.cs
@@ -0,0 +1,286 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace StackEgg
+{
+ public class StackEggGame
+ {
+ public int DaysElapsed { get; set; }
+
+ public StackEggPhase.Id CurrentPhaseId { get; set; }
+ public StackEggPhase CurrentPhase { get { return StackEggPhase.ById(CurrentPhaseId); } }
+
+ public Dictionary Stats { get; set; }
+
+ public Dictionary AccumulatedStats { get; set; }
+
+ public int ModeratorFlags { get; set; }
+
+ public bool IsFailing { get; set; }
+
+ ///
+ /// equal to CurrentPhaseId if the game is still active
+ ///
+ public StackEggPhase.Id ReachedActivePhaseId { get; set; }
+
+ public StackEggGame Initialize()
+ {
+ DaysElapsed = 0;
+ CurrentPhaseId = ReachedActivePhaseId = StackEggPhase.Id.PrivateBeta;
+
+ Stats = new Dictionary()
+ {
+ { StackEggStat.Questions, 0.0 },
+ { StackEggStat.Answers, 0.0 },
+ { StackEggStat.Quality, 0.0 },
+ { StackEggStat.Users, 0.0 },
+ { StackEggStat.Traffic, 0.0 }
+ };
+
+ AccumulatedStats = new Dictionary()
+ {
+ { StackEggStat.Questions, 0 },
+ { StackEggStat.Answers, 0 },
+ { StackEggStat.Quality, 0 },
+ { StackEggStat.Users, 0 },
+ { StackEggStat.Traffic, 0 }
+ };
+
+ ModeratorFlags = 0;
+ //AdvanceToNextPhase(); AdvanceToNextPhase();
+
+ return this;
+ }
+
+ public static int UsefulRound(double x)
+ {
+ return (int)Math.Round(x, MidpointRounding.AwayFromZero);
+ }
+
+ ///
+ /// rounds a stat to the number of hearts (zero to four) that should be displayed to the user
+ ///
+ public static int HeartRound(double x)
+ {
+ return Math.Min(4, Math.Max(0, UsefulRound(x)));
+ }
+
+ ///
+ /// Like HeartRound, but can return -1 (which to the user would cause a red "dangerously low" warning)
+ ///
+ public static int HeartRoundExtended(double x)
+ {
+ return Math.Min(4, Math.Max(-1, UsefulRound(x)));
+ }
+
+ private static double CapStat(double x, bool atZero)
+ {
+ return Math.Min(4.5, Math.Max(atZero ? -0.49 : -1.5, x));
+ }
+
+ public IEnumerable AvailableActions()
+ {
+ if (CurrentPhase == null)
+ yield break;
+
+ if (!IsFailing)
+ {
+ foreach (var action in CurrentPhase.AvailableActions)
+ yield return action;
+ }
+
+ if (ModeratorFlags > 0)
+ yield return StackEggAction.FlagForModerator;
+
+ yield return StackEggAction.Nothing;
+ }
+
+ public bool ActionIsLegal(StackEggAction action)
+ {
+ if (CurrentPhase == null)
+ return false;
+ switch (action)
+ {
+ case StackEggAction.Nothing: return true;
+ case StackEggAction.FlagForModerator: return ModeratorFlags > 0;
+ default: return !IsFailing && CurrentPhase.AvailableActions.Contains(action);
+ }
+ }
+
+ public IEnumerable BadStats()
+ {
+ return StatsLessThan(-0.5);
+ }
+ public IEnumerable CriticalStats()
+ {
+ return StatsLessThan(-1.5);
+ }
+
+ public IEnumerable StatsLessThan(double x)
+ {
+ return StatsAvailableNowOrAtTheEnd().Where(s => Stats[s] <= x);
+ }
+
+ private void PerformBaseAction(StackEggAction action)
+ {
+ var phase = CurrentPhase;
+ var newValues = new Dictionary();
+ foreach (var influencedStat in phase.AvailableStats)
+ {
+ var oldValue = Stats[influencedStat];
+ var newValue = oldValue;
+ bool anyInfluence = false,
+ anyPositiveActionInfluence = false;
+ var statInfluence = phase.StatInfluences[influencedStat];
+ if (statInfluence.ActionInfluences != null && statInfluence.ActionInfluences.ContainsKey(action))
+ {
+ var change = statInfluence.ActionInfluences[action];
+ newValue = oldValue + change;
+ if (change > 0)
+ {
+ // see the guarantee in the description of StackEggStatInfluence.ActionInfluences
+ newValue = Math.Max(newValue, HeartRound(oldValue) + 1);
+ anyPositiveActionInfluence = true;
+ }
+ anyInfluence = true;
+ }
+
+ if (statInfluence.StatInfluences != null && !anyPositiveActionInfluence)
+ {
+ foreach (var kvp in statInfluence.StatInfluences)
+ {
+ var influencingStat = kvp.Key;
+ var arrayIndex = HeartRoundExtended(Stats[influencingStat]) + 1;
+ var change = kvp.Value[arrayIndex];
+ if (change.HasValue)
+ {
+ newValue += change.Value;
+ anyInfluence = true;
+ }
+ }
+ }
+
+ if (!anyInfluence)
+ {
+ newValue = oldValue + statInfluence.IdleInfluence;
+ }
+
+ newValues[influencedStat] = CapStat(newValue, statInfluence.CapAtZero);
+ }
+
+ foreach (var stat in phase.AvailableStats)
+ {
+ Stats[stat] = newValues[stat];
+ }
+ }
+
+ private void FlagForModerator()
+ {
+ foreach (var stat in CurrentPhase.AvailableStats)
+ {
+ Stats[stat] = Math.Max(Stats[stat], 1);
+ }
+ ModeratorFlags--;
+ }
+
+ ///
+ /// Returns true if we're advancing to a new phase, false otherwise
+ ///
+ ///
+ ///
+ public bool AdvanceDay(StackEggAction performedAction)
+ {
+ if (!ActionIsLegal(performedAction))
+ throw new InvalidOperationException(performedAction + " is not a legal action at this time");
+
+ var critical = CriticalStats().ToList();
+
+ switch (performedAction)
+ {
+ case StackEggAction.Ask:
+ case StackEggAction.Answer:
+ case StackEggAction.Upvote:
+ case StackEggAction.Downvote:
+ case StackEggAction.Close:
+ case StackEggAction.Nothing:
+ PerformBaseAction(performedAction);
+ break;
+ case StackEggAction.FlagForModerator:
+ FlagForModerator();
+ break;
+ }
+
+ foreach (var stat in CurrentPhase.AvailableStats)
+ {
+ AccumulatedStats[stat] += HeartRound(Stats[stat]);
+ }
+
+ DaysElapsed++;
+
+ var stillCritical = critical.Intersect(CriticalStats()).ToList();
+ if (stillCritical.Count > 0)
+ {
+ if (!IsFailing && ModeratorFlags > 0)
+ {
+ IsFailing = true;
+ return false;
+ }
+ else
+ {
+ CurrentPhaseId = StackEggPhase.Id.Failed;
+ return true;
+ }
+ }
+
+ IsFailing = false;
+
+ if (GetPhaseProgress() >= 1.0)
+ {
+ AdvanceToNextPhase();
+ return true;
+ }
+ return false;
+ }
+
+ public IEnumerable StatsAvailableNowOrAtTheEnd()
+ {
+ if (CurrentPhase != null)
+ return CurrentPhase.AvailableStats;
+
+ return StackEggPhase.ById(ReachedActivePhaseId).AvailableStats;
+ }
+
+ private void AdvanceToNextPhase()
+ {
+ var oldPhase = CurrentPhase;
+ CurrentPhaseId = oldPhase.NextPhaseId;
+
+ if (CurrentPhase != null)
+ {
+ ModeratorFlags++;
+ ReachedActivePhaseId = CurrentPhaseId;
+
+ // stats that were available in the old phase are set to 1, the others stay at 0
+ // (note we're relying on the fact that AvailableStats only gets new stats, but never
+ // has stats removed)
+ foreach (var oldStat in oldPhase.AvailableStats)
+ {
+ Stats[oldStat] = 1.0;
+ AccumulatedStats[oldStat] = 0;
+ }
+ }
+ }
+
+ public double GetPhaseProgress()
+ {
+ if (CurrentPhase == null)
+ return 0.0;
+
+ var goals = CurrentPhase.StatGoals;
+ if (goals == null || goals.Count == 0)
+ return 0.0;
+ return goals.Average(kvp => Math.Min(1.0, (double)AccumulatedStats[kvp.Key] / kvp.Value));
+ }
+ }
+}
\ No newline at end of file
diff --git a/StackEgg/StackEggPhase.cs b/StackEgg/StackEggPhase.cs
new file mode 100644
index 0000000..ce3db72
--- /dev/null
+++ b/StackEgg/StackEggPhase.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace StackEgg
+{
+ public class StackEggPhase
+ {
+ public enum Id
+ {
+ PrivateBeta = 1,
+ PublicBeta,
+ Launched,
+
+ Failed = 98,
+ WonTheInternet = 99,
+ }
+
+ ///
+ /// The stats that are used during this phase
+ ///
+ public StackEggStat[] AvailableStats { get; set; }
+
+ ///
+ /// The base actions available during this phase
+ ///
+ public StackEggAction[] AvailableActions { get; set; }
+
+ public Dictionary StatInfluences { get; set; }
+
+ ///
+ /// The criteria required to reach the next phase. All stat goals must be reached to
+ /// advance to the next round. The number this is checked against is the user-visible stat
+ /// (i.e. zero to four hearts), integrated over the days. E.g. two days at two hearts and one
+ /// day at three hearts gets you seven points closer to the goal.
+ ///
+ public Dictionary StatGoals { get; set; }
+
+ ///
+ /// The phase that the game will advance to when all goals are met.
+ ///
+ public Id NextPhaseId { get; set; }
+
+
+ public static StackEggPhase ById(Id id)
+ {
+ switch (id)
+ {
+ case Id.PrivateBeta: return PrivateBeta;
+ case Id.PublicBeta: return PublicBeta;
+ case Id.Launched: return Launched;
+ case Id.WonTheInternet:
+ case Id.Failed:
+ return null;
+ }
+ throw new ArgumentOutOfRangeException("id");
+ }
+
+
+
+ public static StackEggPhase PrivateBeta = new StackEggPhase
+ {
+ AvailableStats = new StackEggStat[] { StackEggStat.Questions, StackEggStat.Answers },
+ AvailableActions = new StackEggAction[] { StackEggAction.Ask, StackEggAction.Answer },
+ StatInfluences = new Dictionary()
+ {
+ {
+ StackEggStat.Questions, new StackEggStatInfluence
+ {
+ // asking increases the question stat
+ ActionInfluences = new Dictionary() { { StackEggAction.Ask, 1.0 } },
+
+ StatInfluences = null,
+
+ IdleInfluence = -0.3
+
+ }
+ },
+ {
+ StackEggStat.Answers, new StackEggStatInfluence
+ {
+ // answering increases the answers stat
+ ActionInfluences = new Dictionary() { { StackEggAction.Answer, 1.0 } },
+
+ StatInfluences = new Dictionary()
+ {
+ // a low question count causes a decrease in answers
+ { StackEggStat.Questions, new double?[] { -1.0, -0.5, null, null, null, null } }
+ },
+
+ IdleInfluence = -0.3
+ }
+ }
+ },
+ StatGoals = new Dictionary()
+ {
+ { StackEggStat.Questions, 50 },
+ { StackEggStat.Answers, 50 }
+ },
+ NextPhaseId = Id.PublicBeta
+ }; // end private beta
+
+
+ public static StackEggPhase PublicBeta = new StackEggPhase
+ {
+ AvailableStats = new StackEggStat[] { StackEggStat.Questions, StackEggStat.Answers, StackEggStat.Users, StackEggStat.Quality },
+ AvailableActions = new StackEggAction[] { StackEggAction.Ask, StackEggAction.Answer, StackEggAction.Upvote, StackEggAction.Downvote },
+ StatInfluences = new Dictionary()
+ {
+ {
+ StackEggStat.Questions, new StackEggStatInfluence
+ {
+ // asking increases the question stat
+ ActionInfluences = new Dictionary() { { StackEggAction.Ask, 1.0 } },
+
+ StatInfluences = null,
+
+ IdleInfluence = -0.3
+
+ }
+ },
+ {
+ StackEggStat.Answers, new StackEggStatInfluence
+ {
+ // answering increases the answers stat
+ ActionInfluences = new Dictionary() { { StackEggAction.Answer, 1.0 } },
+
+ StatInfluences = new Dictionary()
+ {
+ // A low question count causes a decrease in answers. These value are a little bit harder than in private beta
+ { StackEggStat.Questions, new double?[] { -1.0, -0.5, -0.4, null, null, null } }
+ },
+
+ IdleInfluence = -0.3
+ }
+ },
+ {
+ StackEggStat.Users, new StackEggStatInfluence
+ {
+ // upvoting increases the users stat; downvoting decreases it
+ ActionInfluences = new Dictionary() { { StackEggAction.Upvote, 1.0 }, { StackEggAction.Downvote, -0.5 } },
+
+ StatInfluences = null,
+
+ IdleInfluence = -0.3
+ }
+ },
+ {
+ StackEggStat.Quality, new StackEggStatInfluence
+ {
+ // downvoting increases the quality stat
+ ActionInfluences = new Dictionary() { { StackEggAction.Downvote, 1.0 } },
+
+ StatInfluences = null,
+
+ IdleInfluence = -0.3
+
+ }
+ }
+ },
+ StatGoals = new Dictionary()
+ {
+ { StackEggStat.Questions, 200 },
+ { StackEggStat.Answers, 200 },
+ { StackEggStat.Users, 200 },
+ { StackEggStat.Quality, 200 }
+ },
+ NextPhaseId = Id.Launched
+ }; // end public beta
+
+
+ public static StackEggPhase Launched = new StackEggPhase
+ {
+ AvailableStats = new StackEggStat[] { StackEggStat.Questions, StackEggStat.Answers, StackEggStat.Users, StackEggStat.Quality, StackEggStat.Traffic },
+ AvailableActions = new StackEggAction[] { StackEggAction.Ask, StackEggAction.Answer, StackEggAction.Upvote, StackEggAction.Downvote, StackEggAction.Close },
+ StatInfluences = new Dictionary()
+ {
+ {
+ StackEggStat.Questions, new StackEggStatInfluence
+ {
+ // asking increases the question stat; closing severly decreases it
+ ActionInfluences = new Dictionary() { { StackEggAction.Ask, 1.0 }, { StackEggAction.Close, -2.0 } },
+
+ StatInfluences = null,
+
+ IdleInfluence = -0.3
+
+ }
+ },
+ {
+ StackEggStat.Answers, new StackEggStatInfluence
+ {
+ // answering increases the answers stat
+ ActionInfluences = new Dictionary() { { StackEggAction.Answer, 1.0 } },
+
+ StatInfluences = new Dictionary()
+ {
+ // A low question count causes a decrease in answers. These value are even harder than in public beta
+ { StackEggStat.Questions, new double?[] { -1.0, -0.8, -0.6, -0.4, null, null } }
+ },
+
+ IdleInfluence = -0.3
+ }
+ },
+ {
+ StackEggStat.Users, new StackEggStatInfluence
+ {
+ // upvoting increases the users stat; downvoting decreases it
+ ActionInfluences = new Dictionary() { { StackEggAction.Upvote, 1.0 }, { StackEggAction.Downvote, -0.5 } },
+
+ StatInfluences = new Dictionary()
+ {
+ // very high traffic brings new users
+ { StackEggStat.Traffic, new double?[] { null, null, null, -0.2, 0.0, 0.2 } }
+ },
+
+ IdleInfluence = -0.3
+ }
+ },
+ {
+ StackEggStat.Quality, new StackEggStatInfluence
+ {
+ // downvoting increases the quality stat, closing even more so
+ ActionInfluences = new Dictionary() { { StackEggAction.Downvote, 1.0 }, { StackEggAction.Close, 2.0 } },
+
+ StatInfluences = new Dictionary()
+ {
+ // very high traffic badly impacts quality
+ { StackEggStat.Traffic, new double?[] { null, null, null, null, -0.7, -0.9 } }
+ },
+
+ IdleInfluence = -0.3
+ }
+ },
+ {
+ StackEggStat.Traffic, new StackEggStatInfluence
+ {
+ CapAtZero = true,
+
+ // traffic cannot be directly influenced
+ ActionInfluences = null,
+
+ StatInfluences = new Dictionary()
+ {
+ // both answers and quality have to be pretty high for traffic growth
+ { StackEggStat.Quality, new double?[] { -0.5, -0.5, -0.5, -0.2, -0.1, 0.1 } },
+ { StackEggStat.Answers, new double?[] { -0.3, -0.3, -0.3, 0, 0.1, 0.3 } }
+ },
+
+ IdleInfluence = 0
+ }
+ }
+ },
+ StatGoals = new Dictionary()
+ {
+ { StackEggStat.Questions, 400 },
+ { StackEggStat.Answers, 400 },
+ { StackEggStat.Users, 400 },
+ { StackEggStat.Quality, 400 },
+ { StackEggStat.Traffic, 300 }
+ },
+ NextPhaseId = Id.WonTheInternet
+ }; // end launched
+
+ }
+}
\ No newline at end of file
diff --git a/StackEgg/StackEggStat.cs b/StackEgg/StackEggStat.cs
new file mode 100644
index 0000000..18684f5
--- /dev/null
+++ b/StackEgg/StackEggStat.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace StackEgg
+{
+ public enum StackEggStat
+ {
+ Questions = 1,
+ Answers,
+ Users,
+ Quality,
+ Traffic
+ }
+}
\ No newline at end of file
diff --git a/StackEgg/StackEggStatInfluence.cs b/StackEgg/StackEggStatInfluence.cs
new file mode 100644
index 0000000..f55968c
--- /dev/null
+++ b/StackEgg/StackEggStatInfluence.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace StackEgg
+{
+ ///
+ /// Specifies how a particular stat is influenced (per day) by the taken action,
+ /// the other stats, and in absence of other influences.
+ ///
+ public class StackEggStatInfluence
+ {
+ ///
+ /// Specifies how the performed action influences the stat. If the influence is positive,
+ /// then the action is guaranteed to make a *user-visible* change (i.e. the stat is increased
+ /// such that there is at least one more full heart). Unless of course there are already
+ /// four full hearts.
+ ///
+ public Dictionary ActionInfluences { get; set; }
+
+ ///
+ /// Specifies how the stat is influenced by the values of other stats. The double?[] must have
+ /// a length of six, corresponding to -1, 0, 1, 2, 3, 4 hearts. Example: If for the influenced stat
+ /// StackEggStat.Answers and the influencing stat StackEggStat.Questions, the value
+ /// is [-1, -.8, -.6, -.4, null, null], and the current questions stat is 1.9: That rounds to 2.
+ /// Thus the question stat causes a decrease in answer stat by 0.4.
+ ///
+ /// StatInfluence is not consulted if the performed action has a positive influence on the influenced stat
+ /// (i.e. in the above example, answers aren't influenced by questions if the performed action was "Answer").
+ ///
+ public Dictionary StatInfluences { get; set; }
+
+ ///
+ /// If the performed action does not influence the stat and there is no influence from other stats, then
+ /// the stat is changed by this amount.
+ ///
+ /// "No influence" does *not* mean "zero"; rather it means the ActionInfluence does not contain the action at all,
+ /// and StatInfluence either doesn't contain anything, or none of the relevant double?s is non-null.
+ ///
+ public double IdleInfluence { get; set; }
+
+ ///
+ /// If this is true, then the stat cannot got to -1 hearts (i.e. no red "dangerously low" warning and the
+ /// stat cannot cause a site shutdown).
+ ///
+ public bool CapAtZero { get; set; }
+ }
+}
\ No newline at end of file