Last active
September 22, 2023 17:19
-
-
Save jlennox/934ac37c8b265ee88456cadcc058b7bd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using OfficeOpenXml; | |
using OfficeOpenXml.Style; | |
using System.Drawing; | |
using System.Text.Json; | |
using System.Text.Json.Serialization; | |
using System.Text.RegularExpressions; | |
// Source code is licensed under MIT 2023 Joseph Lennox | |
// scryfall does not appear to post a license for their data. | |
// This code is a mess designed to quickly gen some info :) | |
namespace MagicSearch; | |
internal class Program | |
{ | |
static void Main(string[] args) | |
{ | |
// "Default cards" database from https://scryfall.com/docs/api/bulk-data | |
const string file = @"C:\Users\joe\Desktop\delete\default-cards-20230921090601.json"; | |
var cards = JsonSerializer.Deserialize<Card[]>(File.OpenRead(file), new JsonSerializerOptions | |
{ | |
PropertyNameCaseInsensitive = true | |
}); | |
var woods = cards.Where(t => t.Name == "Tainted Wood").ToArray(); | |
var lands = cards | |
.Where(t => !t.Reprint) | |
.Where(t => !t.Variation) | |
.Where(t => t.Legalities.PreModernAllowed) | |
.Where(t => t.IsLand) | |
.OrderBy(t => t.ReleasedAt) | |
.ThenBy(t => t.Name) | |
.ToArray(); | |
ExcelPackage.LicenseContext = LicenseContext.NonCommercial; | |
using var package = new ExcelPackage(); | |
var byColorSheet = package.Workbook.Worksheets.Add("By color"); | |
var byColorWithColorlessSheet = package.Workbook.Worksheets.Add("By color (with C)"); | |
var byTypeSheet = package.Workbook.Worksheets.Add("By type"); | |
var byTypeWithColorlessSheet = package.Workbook.Worksheets.Add("By type (with C)"); | |
var produceMultipleSingleMana = package.Workbook.Worksheets.Add("Add multiple single-colored mana"); | |
var produceMultipleMana = package.Workbook.Worksheets.Add("Add multiple mana"); | |
// var byDuelColorSheet = package.Workbook.Worksheets.Add("By duel colors"); | |
var byColorIdentitySheet = package.Workbook.Worksheets.Add("By color identity"); | |
var allLandsSheet = package.Workbook.Worksheets.Add("All lands"); | |
var infoSheet = package.Workbook.Worksheets.Add("Info"); | |
infoSheet.Cells[1, 1].Value = "Likely not up to date source code: https://gist.github.com/jlennox/934ac37c8b265ee88456cadcc058b7bd"; | |
infoSheet.Cells[2, 1].Value = $"Last generate {DateTime.Now}"; | |
infoSheet.Cells[3, 1].Value = $"Database used {Path.GetFileNameWithoutExtension(file)}"; | |
infoSheet.Cells[4, 1].Value = $"Questions/comments/complaints: @fizzbinx on Discord"; | |
foreach (var sheet in new[] { | |
byTypeSheet, byTypeWithColorlessSheet, | |
byColorSheet, byColorWithColorlessSheet, | |
produceMultipleSingleMana, produceMultipleMana, | |
// byDuelColorSheet, | |
byColorIdentitySheet, | |
allLandsSheet }) | |
{ | |
sheet.Column((int)Columns.Name).Width = 20; | |
foreach (var color in new[] { | |
Columns.ColorR, | |
Columns.ColorG, | |
Columns.ColorU, | |
Columns.ColorB, | |
Columns.ColorW, | |
Columns.ColorC | |
}) | |
{ | |
sheet.Column((int)color).Width = 3; | |
} | |
sheet.Column((int)Columns.Set).Width = 15; | |
sheet.Column((int)Columns.Price).Style.HorizontalAlignment = ExcelHorizontalAlignment.Right; | |
sheet.Column((int)Columns.Text).Width = 300; | |
} | |
var duels = lands | |
.Where(DuelCard.IsMatch) | |
.Select(t => new DuelCard(t)) | |
.ToArray(); | |
static void PrintByGrouping( | |
ExcelWorksheet sheet, | |
string description, | |
IEnumerable<IGrouping<string, DuelCard>> grouped, | |
Func<DuelCard, string[]>? getManas = null, | |
bool colorTitle = false) | |
{ | |
var i = 1; | |
sheet.Row(1).Height = 25; | |
var titleCells = sheet.Cells[i, 1, i, (int)Columns.MAX]; | |
titleCells.Merge = true; | |
titleCells.Value = description; | |
titleCells.Style.Font.Size = 15; | |
titleCells.Style.Font.Bold = true; | |
++i; | |
foreach (var group in grouped) | |
{ | |
var titleCell = sheet.Cells[i, 1]; | |
titleCell.Value = group.Key; | |
titleCell.Style.Font.Size = 13; | |
for (var j = 1; j < 5; ++j) sheet.Cells[i, j].Style.Font.Bold = true; | |
if (colorTitle) { | |
var card = group.First().Card; | |
card.AddColorsToWorksheet(sheet, i, card.ColorIdentity); | |
} | |
++i; | |
foreach (var card in group) | |
{ | |
var manas = getManas == null ? card.Manas : getManas(card); | |
card.AddToWorksheet(sheet, i, manas); | |
++i; | |
} | |
++i; | |
++i; | |
} | |
} | |
PrintByGrouping(byTypeSheet, "By duel land type", duels.GroupBy(t => t.GetGroupingKey(false))); | |
PrintByGrouping(byTypeWithColorlessSheet, "By duel land type, including tapping for colorless", duels.GroupBy(t => t.GetGroupingKey(true))); | |
PrintByGrouping(byColorSheet, "By produced colors", duels.GroupBy(t => t.GetColorKey(true)).OrderByDescending(t => t.Key.Length)); | |
PrintByGrouping(byColorWithColorlessSheet, "By produced colors, including colorless", duels.GroupBy(t => t.GetColorKey(false)).OrderByDescending(t => t.Key.Length)); | |
PrintByGrouping( | |
byColorIdentitySheet, | |
"By \"color identity\"", | |
duels.GroupBy(t => t.GetColorIdentity()).OrderByDescending(t => t.Key.Length), | |
null, | |
true); | |
PrintByGrouping(produceMultipleSingleMana, | |
"All mono colored land that produce multiple mana", | |
lands.Where(DuelCard.IsMultiMono).Select(t => new DuelCard(t)).OrderBy(t => t.Manas.First()).GroupBy(t => "")); | |
PrintByGrouping(produceMultipleMana, | |
"All multi-colored land that produce multiple mana", | |
lands.Where(DuelCard.IsMulti).Select(t => new DuelCard(t)).OrderBy(t => t.Manas.First()).GroupBy(t => "")); | |
PrintByGrouping(allLandsSheet, "All lands, even non-mana producing", lands.Select(t => new DuelCard(t)).GroupBy(t => "")); | |
var allDuels = new List<string[]>(); | |
var allColors = new[] { "R", "G", "B", "U", "W" }; | |
foreach (var c1 in allColors) foreach (var c2 in allColors) { | |
if (c1 != c2) | |
{ | |
allDuels.Add(new[] { c1, c2 }.OrderBy(t => t).ToArray()); | |
} | |
} | |
// PrintByGrouping(byDuelColorSheet, | |
// "By each 2 color combination, even if producing more color", | |
// duels.GroupBy( | |
// t => string.Concat(allDuels.FirstOrDefault(cs => t.ContainsMana(cs), new string[] { })) | |
// )); | |
var x = duels.Where(t => t.Card.ColorIdentity.Length > 1).ToArray(); | |
var fileInfo = new FileInfo(@"C:\users\joe\desktop\delete\PMDuelLands.xlsx"); | |
package.SaveAs(fileInfo); | |
} | |
} | |
enum Columns : int | |
{ | |
Name = 1, | |
ColorR, | |
ColorG, | |
ColorU, | |
ColorB, | |
ColorW, | |
ColorC, | |
AfterColor, | |
Set, | |
Price, | |
AfterPrice, | |
Text, | |
MAX | |
} | |
class DuelCard | |
{ | |
public Card Card { get; } | |
public string SingleLineText { get; } | |
public bool Fetch { get; } | |
public bool IsPain { get; } | |
public bool TapsColorless { get; } | |
public bool EtbTapped { get; } | |
public bool DelayedUntap { get; } | |
public bool Tainted { get; } | |
public bool Sacrifice { get; } | |
public bool Converter { get; } | |
public bool Bounce { get; } | |
public string[] Manas { get; } | |
private static readonly Regex _isConverter = new(@"{\d}, {T}: Add {\w}(?: or )?(?:{\w})", RegexOptions.Compiled | RegexOptions.IgnoreCase); | |
private static readonly Regex _isFetch = new(@"Search your library for a (\w+) or (\w+) card", RegexOptions.Compiled | RegexOptions.IgnoreCase); | |
private static readonly Regex _isMultiMono = new(@"(?:({[BGRUW]})\1)|(?:{[BGRUW]} for each)|(?:Add (?:two)|(?:three)(?:four) mana)", RegexOptions.Compiled | RegexOptions.IgnoreCase); | |
private static readonly Regex _isMulti = new(@"{([BGRUW\d])}{(?!\1)[BGRUW\d]}", RegexOptions.Compiled | RegexOptions.IgnoreCase); | |
public DuelCard(Card card) | |
{ | |
Card = card; | |
SingleLineText = card.OracleText.ReplaceLineEndings(" "); | |
Fetch = _isFetch.IsMatch(SingleLineText); | |
IsPain = SingleLineText.Contains("damage", StringComparison.InvariantCultureIgnoreCase); | |
TapsColorless = SingleLineText.Contains("{T}: Add {C}", StringComparison.InvariantCultureIgnoreCase); | |
EtbTapped = SingleLineText.Contains("enters the battlefield tapped", StringComparison.InvariantCultureIgnoreCase); | |
DelayedUntap = SingleLineText.Contains("untap during your", StringComparison.InvariantCultureIgnoreCase); | |
Tainted = SingleLineText.Contains("Activate only if you control a Swamp", StringComparison.InvariantCultureIgnoreCase); | |
Sacrifice = SingleLineText.Contains("Sacrifice", StringComparison.InvariantCultureIgnoreCase); | |
Converter = _isConverter.IsMatch(SingleLineText); | |
Manas = card.ProducedMana; | |
if (SingleLineText.Contains("sacrifice it unless you return a non-Lair land", StringComparison.CurrentCultureIgnoreCase) || | |
SingleLineText.Contains("return an ")) | |
{ | |
Bounce = true; | |
Sacrifice = false; | |
} | |
if (Fetch) | |
{ | |
var landToMana = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) | |
{ | |
{ "Forest", "G" }, | |
{ "Swamp", "B" }, | |
{ "Island", "U" }, | |
{ "Mountain", "R" }, | |
{ "Plains", "W" }, | |
}; | |
var match = _isFetch.Match(SingleLineText); | |
Manas = match.Groups.Cast<Group>().Skip(1).Select(t => landToMana[t.Value]).ToArray(); | |
} | |
} | |
public static bool IsMatch(Card card) | |
{ | |
if (_isFetch.IsMatch(card.OracleText)) return true; | |
return card.ProducedMana != null && card.ProducedMana.Length > 1; | |
} | |
public static bool IsMultiMono(Card card) | |
{ | |
return _isMultiMono.IsMatch(card.OracleText); | |
} | |
public static bool IsMulti(Card card) | |
{ | |
return card.ProducedMana?.Length > 1 && _isMulti.IsMatch(card.OracleText); | |
} | |
public bool ContainsMana(string[] mana) | |
{ | |
return mana.All(t => Manas.Contains(t)); | |
} | |
public string GetGroupingKey(bool includeColorless) | |
{ | |
var list = new List<string>(); | |
if (Fetch) list.Add("Fetch"); | |
if (IsPain) list.Add("Pain"); | |
if (includeColorless && TapsColorless) list.Add("Taps Colorless"); | |
if (EtbTapped) list.Add("ETB Tapped"); | |
if (DelayedUntap) list.Add("Delayed Untap"); | |
if (Tainted) list.Add("Tainted"); | |
if (Bounce) list.Add("Bounce"); | |
if (Sacrifice) list.Add("Sacrifice"); | |
if (Converter) list.Add("Converter"); | |
return list.Count == 0 ? "Misc" : string.Join(", ", list); | |
} | |
public string GetColorKey(bool includeColorless) | |
{ | |
return string.Concat(Manas | |
.Where(t => !includeColorless || t != "C") | |
.OrderBy(t => t)); | |
} | |
public string GetColorIdentity() | |
{ | |
if (Card.ColorIdentity == null) return "None"; | |
return string.Concat(Card.ColorIdentity.OrderBy(t => t)); | |
} | |
public void AddToWorksheet(ExcelWorksheet sheet, int row, string[] colors) | |
{ | |
Card.AddToWorksheet(sheet, row, colors); | |
} | |
} | |
struct ColorInfo | |
{ | |
public string Mana; | |
public Color Color; | |
public Color ForegroundColor; | |
public ColorInfo(string mana, Color color, Color foregroundColor) | |
{ | |
Mana = mana; | |
Color = color; | |
ForegroundColor = foregroundColor; | |
} | |
} | |
class Card | |
{ | |
public string Name { get; set; } | |
[JsonPropertyName("oracle_text")] | |
public string OracleText { get; set; } | |
public bool Reprint { get; set; } | |
public bool Variation { get; set; } | |
public Legalities Legalities { get; set; } | |
[JsonPropertyName("type_line")] | |
public string TypeLine { get; set; } | |
[JsonPropertyName("set_name")] | |
public string SetName { get; set; } | |
[JsonPropertyName("released_at")] | |
public DateOnly ReleasedAt { get; set; } | |
[JsonPropertyName("color_identity")] | |
public string[] ColorIdentity { get; set; } | |
[JsonPropertyName("produced_mana")] | |
public string[] ProducedMana { get; set; } | |
public Dictionary<string, string> Prices { get; set; } | |
public bool IsLand => TypeLine.Contains("Land"); | |
public void AddColorsToWorksheet(ExcelWorksheet sheet, int row, string[] producedMana) | |
{ | |
var colorInfos = new Dictionary<Columns, ColorInfo> | |
{ | |
{ Columns.ColorR, new ColorInfo("R", Color.Red, Color.Black) }, | |
{ Columns.ColorG, new ColorInfo("G", Color.Green, Color.Black) }, | |
{ Columns.ColorU, new ColorInfo("U", Color.Blue, Color.White) }, | |
{ Columns.ColorB, new ColorInfo("B", Color.Black, Color.White) }, | |
{ Columns.ColorW, new ColorInfo("W", Color.FromArgb(230, 230, 230), Color.Black) }, | |
{ Columns.ColorC, new ColorInfo("C", Color.WhiteSmoke, Color.Black) }, | |
}; | |
ExcelRange Set(Columns col, string val) | |
{ | |
var cell = sheet.Cells[row, (int)col]; | |
cell.Value = val; | |
return cell; | |
} | |
void SetColor(Columns col) | |
{ | |
var info = colorInfos[col]; | |
if (producedMana == null || !producedMana.Contains(info.Mana)) return; | |
var cell = Set(col, info.Mana); | |
cell.Style.Fill.SetBackground(info.Color); | |
cell.Style.Font.Color.SetColor(info.ForegroundColor); | |
cell.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center; | |
} | |
SetColor(Columns.ColorR); | |
SetColor(Columns.ColorG); | |
SetColor(Columns.ColorU); | |
SetColor(Columns.ColorB); | |
SetColor(Columns.ColorW); | |
SetColor(Columns.ColorC); | |
} | |
public void AddToWorksheet(ExcelWorksheet sheet, int row, string[] producedMana) | |
{ | |
ExcelRange Set(Columns col, string val) | |
{ | |
var cell = sheet.Cells[row, (int)col]; | |
cell.Value = val; | |
return cell; | |
} | |
Set(Columns.Name, Name); | |
AddColorsToWorksheet(sheet, row, producedMana); | |
Set(Columns.Set, SetName); | |
Set(Columns.Price, "$" + Prices.GetValueOrDefault("usd", "?")); | |
Set(Columns.Text, OracleText.ReplaceLineEndings(" ")); | |
} | |
} | |
class Legalities | |
{ | |
public string OldSchool { get; set; } | |
public string PreModern { get; set; } | |
public bool OldSchoolAllowed => OldSchool == "legal"; | |
public bool PreModernAllowed => PreModern == "legal"; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment