mirror of
https://github.com/OatmealDome/Rotationator.git
synced 2026-03-21 17:34:16 -05:00
Program: Implement basic functionality
This commit is contained in:
parent
54bacb4553
commit
dba69ac116
37
Rotationator/GambitStageInfo.cs
Normal file
37
Rotationator/GambitStageInfo.cs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
namespace Rotationator;
|
||||
|
||||
public class GambitStageInfo
|
||||
{
|
||||
public VersusRule Rule
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public List<int> Stages
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public GambitStageInfo()
|
||||
{
|
||||
Rule = VersusRule.None;
|
||||
Stages = new List<int>();
|
||||
}
|
||||
|
||||
public dynamic ToByamlStagesList()
|
||||
{
|
||||
List<dynamic> byamlStages = new List<dynamic>();
|
||||
|
||||
foreach (int id in Stages)
|
||||
{
|
||||
byamlStages.Add(new Dictionary<string, dynamic>()
|
||||
{
|
||||
{ "MapID", id }
|
||||
});
|
||||
}
|
||||
|
||||
return byamlStages;
|
||||
}
|
||||
}
|
||||
73
Rotationator/GambitVersusPhase.cs
Normal file
73
Rotationator/GambitVersusPhase.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
namespace Rotationator;
|
||||
|
||||
public class GambitVersusPhase
|
||||
{
|
||||
public int Length
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public GambitStageInfo RegularInfo
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public GambitStageInfo GachiInfo
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public GambitVersusPhase()
|
||||
{
|
||||
Length = 0;
|
||||
RegularInfo = new GambitStageInfo();
|
||||
GachiInfo = new GambitStageInfo();
|
||||
}
|
||||
|
||||
public GambitVersusPhase(dynamic byamlPhase)
|
||||
{
|
||||
Length = byamlPhase["Time"];
|
||||
|
||||
dynamic byamlRegularStages = byamlPhase["RegularStages"];
|
||||
|
||||
RegularInfo = new GambitStageInfo()
|
||||
{
|
||||
Rule = VersusRuleUtil.FromEnumString(byamlPhase["RegularRule"]),
|
||||
Stages = new List<int>()
|
||||
{
|
||||
byamlRegularStages[0]["MapID"],
|
||||
byamlRegularStages[1]["MapID"]
|
||||
}
|
||||
};
|
||||
|
||||
dynamic byamlGachiStages = byamlPhase["GachiStages"];
|
||||
|
||||
GachiInfo = new GambitStageInfo()
|
||||
{
|
||||
Rule = VersusRuleUtil.FromEnumString(byamlPhase["GachiRule"]),
|
||||
Stages = new List<int>()
|
||||
{
|
||||
byamlGachiStages[0]["MapID"],
|
||||
byamlGachiStages[1]["MapID"]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public dynamic ToByamlPhase()
|
||||
{
|
||||
dynamic byamlPhase = new Dictionary<string, dynamic>();
|
||||
|
||||
byamlPhase["Time"] = Length;
|
||||
|
||||
byamlPhase["GachiRule"] = GachiInfo.Rule.ToEnumString();
|
||||
byamlPhase["GachiStages"] = GachiInfo.ToByamlStagesList();
|
||||
|
||||
byamlPhase["RegularRule"] = RegularInfo.Rule.ToEnumString();
|
||||
byamlPhase["RegularStages"] = RegularInfo.ToByamlStagesList();
|
||||
|
||||
return byamlPhase;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,273 @@
|
|||
// See https://aka.ms/new-console-template for more information
|
||||
using System.CommandLine;
|
||||
using System.Text.Json;
|
||||
using OatmealDome.BinaryData;
|
||||
using OatmealDome.NinLib.Byaml;
|
||||
using OatmealDome.NinLib.Byaml.Dynamic;
|
||||
using Rotationator;
|
||||
|
||||
Console.WriteLine("Hello, World!");
|
||||
//
|
||||
// Constants
|
||||
//
|
||||
|
||||
const int maximumPhases = 192; // TODO correct?
|
||||
|
||||
Dictionary<VersusRule, List<int>> bannedStages = new Dictionary<VersusRule, List<int>>()
|
||||
{
|
||||
{
|
||||
VersusRule.Paint,
|
||||
new List<int>() // nothing banned
|
||||
},
|
||||
{
|
||||
VersusRule.Goal,
|
||||
new List<int>()
|
||||
{
|
||||
2, // Saltspray Rig
|
||||
4, // Blackbelly Skatepark
|
||||
14 // Piranha Pit
|
||||
}
|
||||
},
|
||||
{
|
||||
VersusRule.Area,
|
||||
new List<int>() // nothing banned
|
||||
},
|
||||
{
|
||||
VersusRule.Lift,
|
||||
new List<int>()
|
||||
{
|
||||
2, // Saltspray Rig
|
||||
6, // Port Mackerel
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
List<int> defaultStagePool = new List<int>()
|
||||
{
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
||||
};
|
||||
|
||||
List<VersusRule> defaultGachiRulePool = new List<VersusRule>()
|
||||
{
|
||||
VersusRule.Goal,
|
||||
VersusRule.Area,
|
||||
VersusRule.Lift
|
||||
};
|
||||
|
||||
Random random = new Random();
|
||||
|
||||
//
|
||||
// Command handling
|
||||
//
|
||||
|
||||
Argument<string> lastByamlArg = new Argument<string>("lastByaml", "The last VSSetting BYAML file.");
|
||||
|
||||
Argument<string> outputByamlArg = new Argument<string>("lastByaml", "The output VSSetting BYAML file.");
|
||||
|
||||
Command command = new RootCommand("Generates a new VSSetting BYAMl file.")
|
||||
{
|
||||
lastByamlArg,
|
||||
outputByamlArg
|
||||
};
|
||||
|
||||
command.SetHandler((lastPath, outPath) => Run(lastPath, outPath), lastByamlArg, outputByamlArg);
|
||||
|
||||
command.Invoke(args);
|
||||
|
||||
//
|
||||
// Entrypoint
|
||||
//
|
||||
|
||||
void Run(string lastByamlPath, string outputByamlPath)
|
||||
{
|
||||
Console.WriteLine("run");
|
||||
|
||||
dynamic lastByaml = ByamlFile.Load(lastByamlPath);
|
||||
|
||||
DateTime lastBaseTime = DateTime.Parse(lastByaml["DateTime"]).ToUniversalTime();
|
||||
List<dynamic> lastPhases = lastByaml["Phases"];
|
||||
|
||||
//
|
||||
// Find phase start point
|
||||
//
|
||||
|
||||
DateTime loopTime = lastBaseTime;
|
||||
DateTime referenceNow = DateTime.UtcNow;
|
||||
|
||||
int lastPhasesStartIdx = -1;
|
||||
|
||||
for (int i = 0; i < lastPhases.Count; i++)
|
||||
{
|
||||
Dictionary<string, dynamic> phase = lastPhases[i];
|
||||
|
||||
DateTime phaseEndTime = loopTime.AddHours((int)phase["Time"]);
|
||||
|
||||
if (referenceNow >= loopTime && phaseEndTime > referenceNow)
|
||||
{
|
||||
lastPhasesStartIdx = i;
|
||||
break;
|
||||
}
|
||||
|
||||
loopTime = phaseEndTime;
|
||||
}
|
||||
|
||||
DateTime baseTime;
|
||||
List<GambitVersusPhase> currentPhases;
|
||||
|
||||
if (lastPhasesStartIdx != -1)
|
||||
{
|
||||
baseTime = loopTime;
|
||||
currentPhases = lastPhases.Skip(lastPhasesStartIdx).Select(p => new GambitVersusPhase(p)).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("not supported yet");
|
||||
}
|
||||
|
||||
// The last phase is set to 10 years, so correct this to the correct phase length.
|
||||
currentPhases.Last().Length = 4;
|
||||
|
||||
//
|
||||
// Generate new phases to fill out the schedule
|
||||
//
|
||||
|
||||
List<VersusRule> gachiRulePool = new List<VersusRule>();
|
||||
Dictionary<VersusRule, List<int>> stagePools = new Dictionary<VersusRule, List<int>>()
|
||||
{
|
||||
{ VersusRule.Paint, new List<int>() },
|
||||
{ VersusRule.Goal, new List<int>() },
|
||||
{ VersusRule.Area, new List<int>() },
|
||||
{ VersusRule.Lift, new List<int>() }
|
||||
};
|
||||
|
||||
for (int i = currentPhases.Count; i < maximumPhases; i++)
|
||||
{
|
||||
GambitVersusPhase currentPhase = new GambitVersusPhase();
|
||||
GambitVersusPhase lastPhase = i != 0 ? currentPhases[i - 1] : new GambitVersusPhase();
|
||||
|
||||
VersusRule gachiRule = PickGachiRule(currentPhase.GachiInfo, lastPhase.GachiInfo, gachiRulePool);
|
||||
|
||||
currentPhase.RegularInfo.Rule = VersusRule.Paint;
|
||||
currentPhase.RegularInfo.Stages.Add(PickStage(currentPhase, lastPhase, VersusRule.Paint,
|
||||
stagePools[VersusRule.Paint]));
|
||||
currentPhase.RegularInfo.Stages.Add(PickStage(currentPhase, lastPhase, VersusRule.Paint,
|
||||
stagePools[VersusRule.Paint]));
|
||||
currentPhase.RegularInfo.Stages.Sort();
|
||||
|
||||
currentPhase.GachiInfo.Rule = gachiRule;
|
||||
currentPhase.GachiInfo.Stages.Add(PickStage(currentPhase, lastPhase, gachiRule, stagePools[gachiRule]));
|
||||
currentPhase.GachiInfo.Stages.Add(PickStage(currentPhase, lastPhase, gachiRule, stagePools[gachiRule]));
|
||||
currentPhase.GachiInfo.Stages.Sort();
|
||||
|
||||
currentPhase.Length = 4;
|
||||
|
||||
currentPhases.Add(currentPhase);
|
||||
}
|
||||
|
||||
//
|
||||
// Write BYAML
|
||||
//
|
||||
|
||||
// As a fallback in case the schedule isn't updated in time, make the last phase 10 years long.
|
||||
currentPhases.Last().Length = 24 * 365 * 10;
|
||||
|
||||
// Set the new base DateTime (this is usually in the JST time zone, but it accepts UTC time as well).
|
||||
lastByaml["DateTime"] = baseTime.ToString("yyyy-MM-dd'T'HH:mm:ssK");
|
||||
|
||||
// Set the new phases.
|
||||
lastByaml["Phases"] = currentPhases.Select(p => p.ToByamlPhase());
|
||||
|
||||
// Add some metadata about this BYAML file and how it was built.
|
||||
lastByaml["ByamlInfo"] = new Dictionary<string, dynamic>()
|
||||
{
|
||||
{ "Generator", "Rotationator 1" },
|
||||
{ "GenerationTime", referenceNow.ToString("O") },
|
||||
{ "BaseByamlStartTime", baseTime.ToString("O") },
|
||||
};
|
||||
|
||||
ByamlFile.Save(outputByamlPath, lastByaml, new ByamlSerializerSettings()
|
||||
{
|
||||
ByteOrder = ByteOrder.BigEndian,
|
||||
SupportsBinaryData = false,
|
||||
Version = ByamlVersion.One
|
||||
});
|
||||
|
||||
File.WriteAllText(outputByamlPath + ".json", JsonSerializer.Serialize(lastByaml, new JsonSerializerOptions()
|
||||
{
|
||||
WriteIndented = true
|
||||
}));
|
||||
}
|
||||
|
||||
//
|
||||
// Utility function to pick a random element from a pool.
|
||||
//
|
||||
|
||||
T GetRandomElementFromPool<T>(List<T> pool, Func<T, bool> validityChecker)
|
||||
{
|
||||
T element;
|
||||
|
||||
do
|
||||
{
|
||||
element = pool[random.Next(0, pool.Count)];
|
||||
} while (!validityChecker(element));
|
||||
|
||||
pool.Remove(element);
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
//
|
||||
// Random stage + rule pickers.
|
||||
//
|
||||
|
||||
VersusRule PickGachiRule(GambitStageInfo stageInfo, GambitStageInfo lastStageInfo, List<VersusRule> pool)
|
||||
{
|
||||
if (pool.Count == 0)
|
||||
{
|
||||
pool.AddRange(defaultGachiRulePool);
|
||||
}
|
||||
|
||||
return GetRandomElementFromPool(pool, rule => rule != lastStageInfo.Rule);
|
||||
}
|
||||
|
||||
int PickStage(GambitVersusPhase phase, GambitVersusPhase lastPhase, VersusRule rule, List<int> pool)
|
||||
{
|
||||
List<int> bannedStagesForRule = bannedStages[rule];
|
||||
|
||||
if (pool.Count == 0)
|
||||
{
|
||||
pool.AddRange(defaultStagePool.Except(bannedStagesForRule));
|
||||
}
|
||||
|
||||
bool IsStageValid(int stageId)
|
||||
{
|
||||
// Don't pick this stage if it's already used in this phase.
|
||||
if (phase.RegularInfo.Stages.Contains(stageId) || phase.GachiInfo.Stages.Contains(stageId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't pick this stage if it's present in the last phase.
|
||||
if (lastPhase.RegularInfo.Stages.Contains(stageId) || lastPhase.GachiInfo.Stages.Contains(stageId))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if all of our options are invalid.
|
||||
if (pool.All(i => !IsStageValid(i)))
|
||||
{
|
||||
// If so, pick a random stage from the default pool, excluding:
|
||||
// - the current phase's stages (in both Regular and Gachi)
|
||||
// - the last phase's stages (in both Regular and Gachi)
|
||||
// - all banned stages for this rule
|
||||
pool = defaultStagePool.Except(phase.RegularInfo.Stages)
|
||||
.Except(phase.GachiInfo.Stages)
|
||||
.Except(lastPhase.RegularInfo.Stages)
|
||||
.Except(lastPhase.GachiInfo.Stages)
|
||||
.Except(bannedStagesForRule)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return GetRandomElementFromPool(pool, IsStageValid);
|
||||
}
|
||||
26
Rotationator/VersusRule.cs
Normal file
26
Rotationator/VersusRule.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
namespace Rotationator;
|
||||
|
||||
public enum VersusRule
|
||||
{
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Turf War
|
||||
/// </summary>
|
||||
Paint,
|
||||
|
||||
/// <summary>
|
||||
/// Rainmaker
|
||||
/// </summary>
|
||||
Goal,
|
||||
|
||||
/// <summary>
|
||||
/// Splat Zones
|
||||
/// </summary>
|
||||
Area,
|
||||
|
||||
/// <summary>
|
||||
/// Tower Control
|
||||
/// </summary>
|
||||
Lift
|
||||
}
|
||||
40
Rotationator/VersusRuleUtil.cs
Normal file
40
Rotationator/VersusRuleUtil.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
namespace Rotationator;
|
||||
|
||||
public static class VersusRuleUtil
|
||||
{
|
||||
public static VersusRule FromEnumString(string str)
|
||||
{
|
||||
switch (str)
|
||||
{
|
||||
case "cPnt":
|
||||
return VersusRule.Paint;
|
||||
case "cVgl":
|
||||
return VersusRule.Goal;
|
||||
case "cVar":
|
||||
return VersusRule.Area;
|
||||
case "cVlf":
|
||||
return VersusRule.Lift;
|
||||
default: // "cNone"
|
||||
return VersusRule.None;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToEnumString(this VersusRule rule)
|
||||
{
|
||||
switch (rule)
|
||||
{
|
||||
case VersusRule.None:
|
||||
return "cNone";
|
||||
case VersusRule.Paint:
|
||||
return "cPnt";
|
||||
case VersusRule.Goal:
|
||||
return "cVgl";
|
||||
case VersusRule.Area:
|
||||
return "cVar";
|
||||
case VersusRule.Lift:
|
||||
return "cVlf";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(rule), "VersusRule not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user