Browse Source

- 0.1 working version

master
Dan 4 years ago
parent
commit
21b4afe890
  1. 42
      FABStarterDeckGen/DeckConfig.cs
  2. 325
      FABStarterDeckGen/DeckGenerator.cs
  3. 80
      FABStarterDeckGen/FABCollectionReader.cs
  4. 24
      FABStarterDeckGen/FabStarterDeckGen.csproj
  5. 18
      FABStarterDeckGen/Program.cs
  6. 178
      FABStarterDeckGen/ReferenceCardLoader.cs
  7. 16
      FabStarterDeckGen.sln

42
FABStarterDeckGen/DeckConfig.cs

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace FabStarterDeckGen
{
public partial class DeckConfig
{
[JsonProperty("Hero")]
public string Hero { get; set; }
[JsonProperty("Core")]
public List<CardSpec> Core { get; set; }
[JsonProperty("Variables")]
public List<CardSpec> Variables { get; set; }
}
public partial class CardSpec
{
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("IsPitchless")]
public bool IsPitchless { get; set; }
[JsonProperty("Count", NullValueHandling = NullValueHandling.Ignore)]
public string Count { get; set; }
[JsonProperty("R", NullValueHandling = NullValueHandling.Ignore)]
public string R { get; set; }
[JsonProperty("Y", NullValueHandling = NullValueHandling.Ignore)]
public string Y { get; set; }
[JsonProperty("B", NullValueHandling = NullValueHandling.Ignore)]
public string B { get; set; }
}
}

325
FABStarterDeckGen/DeckGenerator.cs

@ -0,0 +1,325 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace FabStarterDeckGen
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
public partial class DeckConfig
{
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("Hero")]
public string Hero { get; set; }
[JsonProperty("Core")]
public CardSpec[] Core { get; set; }
[JsonProperty("Variables")]
public CardSpec[] Variables { get; set; }
}
public partial class CardSpec
{
[JsonProperty("Name")]
public string Name { get; set; }
[JsonProperty("IsPitchless")]
public bool IsPitchless { get; set; }
[JsonProperty("Count", NullValueHandling = NullValueHandling.Ignore)]
public string Count { get; set; }
[JsonProperty("R", NullValueHandling = NullValueHandling.Ignore)]
public string R { get; set; }
[JsonProperty("Y", NullValueHandling = NullValueHandling.Ignore)]
public string Y { get; set; }
[JsonProperty("B", NullValueHandling = NullValueHandling.Ignore)]
public string B { get; set; }
}
public class DeckGenerator
{
static IEnumerable<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> list, int length)
{
if (length == 1)
return list.Select(t => new T[] { t });
return GetPermutations(list, length - 1).SelectMany(t => list.Where(o => !t.Contains(o)), (t1, t2) => t1.Concat(new T[] { t2 }));
}
static IEnumerable<IEnumerable<T>> GetPermutationsWithRept<T>(IEnumerable<T> list, int length)
{
if (length == 1)
return list.Select(t => new T[] { t });
return GetPermutationsWithRept(list, length - 1).SelectMany(t => list, (t1, t2) => t1.Concat(new T[] { t2 }));
}
static IEnumerable<IEnumerable<T>> GetKCombsWithRept<T>(IEnumerable<T> list, int length) where T : IComparable
{
if (length == 1) return list.Select(t => new T[] { t });
return GetKCombsWithRept(list, length - 1)
.SelectMany(t => list.Where(o => o.CompareTo(t.Last()) >= 0),
(t1, t2) => t1.Concat(new T[] { t2 }));
}
static IEnumerable<IEnumerable<T>> CartesianProduct<T> (IEnumerable<IEnumerable<T>> sequences)
{
IEnumerable<IEnumerable<T>> emptyProduct =
new[] { Enumerable.Empty<T>() };
return sequences.Aggregate(
emptyProduct,
(accumulator, sequence) =>
from accseq in accumulator
from item in sequence
select accseq.Concat(new[] {item}));
}
private List<DeckConfig>? DeckConfigs = new List<DeckConfig>();
public List<Dictionary<string, int>> DeckLists = new List<Dictionary<string, int>>();
private readonly int REASONABLE_MAX = 10;
private bool IsAssortedThree(string pValue) { return pValue == "3*";}
private bool IsAssortedTwo(string pValue) { return pValue == "2*";}
private bool IsAssortedOne(string pValue) { return pValue == "1*";}
private bool ProcessCountString(string count, ref int rAssorted3Count, ref int rAssorted2Count, ref int rAssorted1Count)
{
if ( count == "" )
return false;
if ( IsAssortedThree(count) )
{
rAssorted3Count++;
return true;
}
if ( IsAssortedTwo(count) )
{
rAssorted2Count++;
return true;
}
if ( IsAssortedOne(count) )
{
rAssorted1Count++;
return true;
}
return false;
}
public void LoadDeckConfigs(string pfilePath)
{
using (StreamReader file = File.OpenText(pfilePath))
{
DeckConfigs = JsonConvert.DeserializeObject <List<DeckConfig>> (file.ReadToEnd());
}
var three_over_two = GetPermutationsWithRept<int>(new[] {1, 2}, 2).Select(c => c.ToList()).ToList();
three_over_two.RemoveAll(i => i.Sum() != 3);
var three_over_three = GetPermutationsWithRept<int>(new[] {0, 1, 2}, 3).Select(c => c.ToList()).ToList();
three_over_three.RemoveAll(i => i.Sum() != 3);
var two_over_two = GetPermutationsWithRept<int>(new[] {0, 1, 2}, 2).Select(c => c.ToList()).ToList();
two_over_two.RemoveAll(i => i.Sum() != 2);
var two_over_three = GetPermutationsWithRept<int>(new[] {0, 1, 2}, 3).Select(c => c.ToList()).ToList();
two_over_three.RemoveAll(i => i.Sum() != 2);
var one_over_two = GetPermutationsWithRept<int>(new[] {0, 1}, 2).Select(c => c.ToList()).ToList();
one_over_two.RemoveAll(i => i.Sum() != 1);
var one_over_three = GetPermutationsWithRept<int>(new[] {0, 1}, 3).Select(c => c.ToList()).ToList();
one_over_three.RemoveAll(i => i.Sum() != 1);
Debug.Assert(DeckConfigs != null, nameof(DeckConfigs) + " != null");
foreach ( var config in DeckConfigs )
{
List<List<List<int>>> variable_card_perm_lists = new List<List<List<int>>>();
foreach ( var variable_card in config.Variables )
{
int assorted_3_count = 0;
int assorted_2_count = 0;
int assorted_1_count = 0;
bool spans_red = ProcessCountString(variable_card.R, ref assorted_3_count, ref assorted_2_count, ref assorted_1_count);
bool spans_blue = ProcessCountString(variable_card.B, ref assorted_3_count, ref assorted_2_count, ref assorted_1_count);
ProcessCountString(variable_card.Y, ref assorted_3_count, ref assorted_2_count, ref assorted_1_count);
List<List<int>> counts = null;
if ( assorted_3_count == 2 )
counts = three_over_two.Select(x => x.ToList()).ToList();
else if (assorted_3_count == 3)
counts = three_over_three.Select(x => x.ToList()).ToList();
else if (assorted_2_count == 2)
counts = two_over_two.Select(x => x.ToList()).ToList();
else if (assorted_2_count == 3)
counts = two_over_three.Select(x => x.ToList()).ToList();
else if (assorted_1_count == 2)
counts = one_over_two.Select(x => x.ToList()).ToList();
else if (assorted_1_count == 3)
counts = one_over_three.Select(x => x.ToList()).ToList();
if ( (!spans_red || !spans_blue) && counts != null )
{
for ( int i = 0; i < counts.Count; i++ )
{
if ( spans_red )
counts[i].Add(int.Parse(variable_card.B));
if (spans_blue)
counts[i].Insert(0,int.Parse(variable_card.R));
}
}
variable_card_perm_lists.Add(counts);
}
var lists = CartesianProduct(variable_card_perm_lists).ToList();
Console.WriteLine(lists.Count);
foreach ( var variation in lists )
{
var card_list = new Dictionary<string, int>();
foreach ( var card in config.Core )
{
if ( card.IsPitchless )
{
card_list.Add(card.Name, int.Parse(card.Count));
}
else
{
card_list.Add(card.Name + " - R", int.Parse(card.R));
card_list.Add(card.Name + " - Y", int.Parse(card.Y));
card_list.Add(card.Name + " - B", int.Parse(card.B));
}
}
var variation_counts = variation.ToList();
for ( int i = 0; i < config.Variables.Length; i++ )
{
var card = config.Variables[i];
var counts = variation_counts[i];
if(counts[0] > 0)
card_list.Add(card.Name + " - R", counts[0]);
if(counts[1] > 0)
card_list.Add(card.Name + " - Y", counts[1]);
if(counts[2] > 0)
card_list.Add(card.Name + " - B", counts[2]);
}
int card_count = card_list.Sum(i => i.Value);
if ( card_count != 45 )
{
Console.WriteLine(card_list.ToString());
Debug.Assert(card_count == 45);
}
DeckLists.Add(card_list);
}
}
}
public void DetermineMaxBuilds(in Dictionary<string, int> pCollectionCounts)
{
var timer = new Stopwatch();
timer.Start();
// Limit this to per hero once i have more specs written
var range = Enumerable.Range(0, DeckLists.Count);
List<int> largest_buildable_combo = new List<int>();
for (int i = 1; i <= REASONABLE_MAX; i++)
{
bool combo_was_built = false;
var combos = GetKCombsWithRept(range, i).Select(c => c.ToList());
var ints = pCollectionCounts;
Parallel.ForEach(combos, (combo, state) =>
{
var temp_card_counts = new Dictionary<string, int>(ints);
if (ComboIsBuildable(combo, temp_card_counts))
{
// Cache it off somehwere as the largest buildable combo
largest_buildable_combo = new List<int>(combo);
// Move on and enlarge the number of decks we are trying to build
combo_was_built = true;
state.Break();
}
});
/*
foreach (var combo in combos)
{
// Multithread!
var temp_card_counts = new Dictionary<string, int>(pCollectionCounts);
if (ComboIsBuildable(combo, temp_card_counts))
{
// Cache it off somehwere as the largest buildable combo
largest_buildable_combo = new List<int>(combo);
// Move on and enlarge the number of decks we are trying to build
combo_was_built = true;
break;
}
}
*/
if (!combo_was_built)
break;
}
Console.WriteLine(string.Join(",", largest_buildable_combo));
Dictionary<string, int> totals_for_combo_build = new Dictionary<string, int>();
foreach (var index in largest_buildable_combo)
{
var deck = DeckLists[index];
foreach (var entry in deck)
{
if (!totals_for_combo_build.ContainsKey(entry.Key))
totals_for_combo_build[entry.Key] = 0;
totals_for_combo_build[entry.Key] += entry.Value;
}
var f = Newtonsoft.Json.JsonConvert.SerializeObject(deck, Newtonsoft.Json.Formatting.Indented);
Console.WriteLine(f);
}
var total_counts = Newtonsoft.Json.JsonConvert.SerializeObject(totals_for_combo_build, Newtonsoft.Json.Formatting.Indented);
Console.WriteLine(total_counts);
timer.Stop();
TimeSpan timeTaken = timer.Elapsed;
Console.WriteLine("Time taken: " + timeTaken.ToString(@"m\:ss\.fff"));
}
private bool DeckIsBuildable(in Dictionary<string, int> pDeckCardCounts, in Dictionary<string, int> pCardCounts)
{
foreach (var required_count in pDeckCardCounts)
{
if (pCardCounts[required_count.Key] - required_count.Value < 0)
return false;
}
return true;
}
private bool ComboIsBuildable(in List<int> pCombination, Dictionary<string, int> pCardCounts)
{
Console.WriteLine("({1})Testing combo: {0}", String.Join(",", pCombination), Thread.CurrentThread.ManagedThreadId);
foreach (var index in pCombination)
{
var deck = DeckLists[index];
if (!DeckIsBuildable(deck, pCardCounts))
return false;
foreach (var required_count in deck)
pCardCounts[required_count.Key] -= required_count.Value;
}
return true;
}
}
}

80
FABStarterDeckGen/FABCollectionReader.cs

@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using IronXL;
using static System.Text.RegularExpressions.Regex;
namespace FabStarterDeckGen
{
public class FABCollectionReader
{
public static readonly string COLUMN_NUMBER = "B";
public static readonly string COLUMN_NAME = "C";
public static readonly string COLUMN_RARITY = "D";
public static readonly string COLUMN_RED = "E";
public static readonly string COLUMN_YELLOW = "F";
public static readonly string COLUMN_BLUE = "G";
public static readonly string[] COLUMNS_PITCHES = { COLUMN_RED, COLUMN_YELLOW, COLUMN_BLUE };
public static readonly string[] COLUMNS_DETAILS = { COLUMN_NUMBER, COLUMN_NAME, COLUMN_RARITY };
public readonly int STARTING_ROW_INDEX = 3;
public Dictionary<string, int> CollectionCounts = new Dictionary<string, int>();
public void LoadCollection(string pFilePath, ReferenceCardLoader pReferenceCard)
{
WorkBook workbook = WorkBook.Load(pFilePath);
foreach (var sheet in workbook.WorkSheets)
{
var current_row_index = STARTING_ROW_INDEX;
var current_card_name = sheet[COLUMN_NAME + current_row_index.ToString()].StringValue;
while (current_card_name != "")
{
Console.WriteLine(current_card_name);
var reference_cards = pReferenceCard.GetCardsWithName(current_card_name);
foreach (var card in reference_cards)
{
if (card.IsPitchless)
{
if( !CollectionCounts.ContainsKey(card.CleanName) )
CollectionCounts.Add(card.CleanName, 0);
CollectionCounts[card.CleanName] += sheet[COLUMN_RED + current_row_index.ToString()].IntValue;
}
else
{
if (card.IsRed)
{
if( !CollectionCounts.ContainsKey(card.CleanNameRed) )
CollectionCounts.Add(card.CleanNameRed, 0);
CollectionCounts[card.CleanNameRed] += sheet[COLUMN_RED + current_row_index.ToString()].IntValue;
}
if (card.IsYellow)
{
if( !CollectionCounts.ContainsKey(card.CleanNameYellow) )
CollectionCounts.Add(card.CleanNameYellow, 0);
CollectionCounts[card.CleanNameYellow] += sheet[COLUMN_YELLOW + current_row_index.ToString()].IntValue;
}
if (card.IsBlue)
{
if( !CollectionCounts.ContainsKey(card.CleanNameBlue) )
CollectionCounts.Add(card.CleanNameBlue, 0);
CollectionCounts[card.CleanNameBlue] += sheet[COLUMN_BLUE + current_row_index.ToString()].IntValue;
}
}
}
current_row_index++;
current_card_name = sheet[COLUMN_NAME + current_row_index.ToString()].StringValue;
}
}
}
}
}

24
FABStarterDeckGen/FabStarterDeckGen.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvHelper" Version="27.2.1" />
<PackageReference Include="IronXL.Excel" Version="2022.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>
<Compile Remove="DeckConfig.cs" />
</ItemGroup>
</Project>

18
FABStarterDeckGen/Program.cs

@ -0,0 +1,18 @@
// See https://aka.ms/new-console-template for more information
using System;
using System.Collections.Generic;
using FabStarterDeckGen;
using Newtonsoft.Json;
string DATA_DIR = @"D:\Dev_P\FabCollectionTracker";
var ref_card_loader = new ReferenceCardLoader();
ref_card_loader.LoadSetCSV(DATA_DIR + @"\csvs\set.csv");
ref_card_loader.LoadCardCSV(DATA_DIR + @"\csvs\card.csv");
var collection_reader = new FABCollectionReader();
collection_reader.LoadCollection(DATA_DIR + @"\FabCollection-Generated.xlsx", ref_card_loader);
var deckGenerator = new DeckGenerator();
deckGenerator.LoadDeckConfigs(DATA_DIR + @"\DeckLists.json");
deckGenerator.DetermineMaxBuilds(collection_reader.CollectionCounts);

178
FABStarterDeckGen/ReferenceCardLoader.cs

@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using CsvHelper;
using CsvHelper.Configuration;
namespace FabStarterDeckGen
{
public class ReferenceCardRow
{
public string Identifiers;
public string Set_Identifiers;
public string Name;
public string Pitch;
public string Rarity;
}
public class ReferenceCardRowMap : ClassMap<ReferenceCardRow>
{
public ReferenceCardRowMap()
{
Map(m => m.Identifiers).Name("Identifiers");
Map(m => m.Set_Identifiers).Name("Set Identifiers");
Map(m => m.Name).Name("Name");
Map(m => m.Pitch).Name("Pitch");
Map(m => m.Rarity).Name("Rarity");
}
}
public class CardInstance
{
private Regex clean_card_name_regex = new Regex(@"[^a-zA-Z0-9_ ]+");
public CardInstance(ReferenceCardRow pBase)
{
Name = pBase.Name;
Pitch = pBase.Pitch;
Rarity = pBase.Rarity;
foreach (var part in pBase.Identifiers.Split(','))
{
if (part == "null")
continue;
Identifiers.Add(part);
}
foreach (var part in pBase.Set_Identifiers.Split(','))
{
if (part == "null")
continue;
Set_Identifiers.Add(part);
}
CleanName = clean_card_name_regex.Replace(Name, "");
}
public List<String> Identifiers = new List<String>();
public List<String> Set_Identifiers = new List<String>();
public string Name;
public string CleanName;
public string Pitch;
public string Rarity;
public bool IsPitchless => Pitch == "";
public bool IsRed => Pitch == "1";
public bool IsYellow => Pitch == "2";
public bool IsBlue => Pitch == "3";
public string CleanNameRed => CleanName + " - R";
public string CleanNameYellow => CleanName + " - Y";
public string CleanNameBlue => CleanName + " - B";
}
public class ReferenceSetRow
{
public string Identifier;
public string Name;
public string Initial_Release_Dates;
}
public class ReferenceSetRowMap : ClassMap<ReferenceSetRow>
{
public ReferenceSetRowMap()
{
Map(m => m.Identifier).Name("Identifier");
Map(m => m.Initial_Release_Dates).Name("Initial Release Dates");
Map(m => m.Name).Name("Name");
}
}
public class SetInstance
{
public SetInstance(ReferenceSetRow pBase)
{
Identifier = pBase.Identifier;
Name = pBase.Name;
var release_dates = new List<DateTime>();
foreach (var part in pBase.Initial_Release_Dates.Split(','))
{
if (part == "null")
continue;
release_dates.Add(DateTime.Parse(part));
}
if (release_dates.Count > 0)
{
release_dates.Sort();
EarliestReleaseDate = release_dates[0];
}
else
{
EarliestReleaseDate = DateTime.UnixEpoch;
}
}
public string Identifier;
public string Name;
public DateTime EarliestReleaseDate;
public bool IsPromoSet;
public bool IsDeckSet;
public bool IsClassicBattleSet;
}
public class ReferenceCardLoader
{
private List<ReferenceSetRow> ReferenceSets;
private List<ReferenceCardRow> ReferenceCards;
public List<SetInstance> Sets = new List<SetInstance>();
public List<CardInstance> Cards = new List<CardInstance>();
public void LoadSetCSV(string pFilePath)
{
var config = new CsvConfiguration(CultureInfo.CurrentCulture) { Delimiter = "\t", Encoding = Encoding.UTF8 };
using (var reader = new StreamReader(pFilePath))
using (var csv = new CsvReader(reader, config))
{
csv.Context.RegisterClassMap<ReferenceSetRowMap>();
ReferenceSets = csv.GetRecords<ReferenceSetRow>().ToList();
}
foreach (var set in ReferenceSets)
{
Sets.Add(new SetInstance(set));
}
}
public void LoadCardCSV(string pFilePath)
{
var config = new CsvConfiguration(CultureInfo.CurrentCulture) { Delimiter = "\t", Encoding = Encoding.UTF8 };
using (var reader = new StreamReader(pFilePath))
using (var csv = new CsvReader(reader, config))
{
csv.Context.RegisterClassMap<ReferenceCardRowMap>();
ReferenceCards = csv.GetRecords<ReferenceCardRow>().ToList();
}
foreach (var card in ReferenceCards)
{
Cards.Add(new CardInstance(card));
}
}
public List<CardInstance> GetCardsWithName(string pName)
{
List<CardInstance> card_list = new List<CardInstance>();
foreach (var card in Cards)
{
if(card.Name != pName)
continue;
card_list.Add(card);
}
return card_list;
}
}
}

16
FabStarterDeckGen.sln

@ -0,0 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FabStarterDeckGen", ".\FabStarterDeckGen\FabStarterDeckGen.csproj", "{FEDDE3A6-84BD-4782-971A-BFFDC6A75C1C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{FEDDE3A6-84BD-4782-971A-BFFDC6A75C1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FEDDE3A6-84BD-4782-971A-BFFDC6A75C1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FEDDE3A6-84BD-4782-971A-BFFDC6A75C1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FEDDE3A6-84BD-4782-971A-BFFDC6A75C1C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
Loading…
Cancel
Save