-
-
Save Xiaoy312/dba9d652dd9cfd22791d856526d1afcf to your computer and use it in GitHub Desktop.
This file contains 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
Category Category2 Category3 English EnglishAlias Chinese ChineseAlias RegexPattern RegexOptions Comment | |
Modifier S. S. \b(S[ \.]|S[nr]\.?|Senior) * i add space for `\b` to works | |
Modifier T${level} T${level} T(?<level>[1-3]) * i add space for `\b` to works | |
Modifier ${quantity}x ${quantity}x \b(?<quantity>\d)(?=[^x\:\d\-]) i add space for `\b` to works | |
Modifier x${quantity} x${quantity} x(?<quantity>\d) i add space for `\b` to works | |
Formatting ", " ", " ?\+ ? | |
Formatting ${floor}: ${floor}: ^(?<floor>\d+(\-\d+)?) ?[:f=\-\.)] * im | |
CommonTypo Aladdin Aladdin alladin i | |
CommonTypo Wyvern Wyvern Wyrven i | |
Glossary Wyvern Strategy 飛龍偷塔 Wyvern Strategy i | |
Unit 5* Orc Naga 蛇神 Naga i | |
Unit 5* Orc Wyvern Rider Wyvern 飛龍騎士 飛龍 Wyvern Rider|Wyvern i | |
Unit 5* Orc Raptor Rider Raptor 猛禽騎士 Raptor Rider|Raptor i | |
Unit 5* Orc Battle Drummer Drummer 戰鼓祭祀 Battle Drummer i | |
Unit 5* Orc Sorcerer 獸族咒巫 Sorcerer|Sorc i | |
Unit 5* Orc Wolf Rider 狼騎 Wolf Rider i | |
Unit 5* Orc Ice Wizard 冰法師 Ice ?Wizard|\bIW\b i | |
Unit 5* Orc Big Foot BF 雪魔 Big ?Foot|BF i | |
Unit 5* Undead Medusa 梅杜莎 Medusa|M[ou]m(my)? i | |
Unit 5* Undead Lich Lich 巫妖 Lich i | |
Unit 5* Undead Dark Admiral Daddy 黑暗提督 Dark Admiral|Admiral|Daddy|\bDad\b i | |
Unit 5* Undead Succubus 魅魔 Succubus|Succ i | |
Unit 5* Undead The Ninja of Darkness Ninja 暗忍 NoD|Ninja i | |
Unit 5* Undead Dark Archer 沈默箭魔 Dark Archer|\bDA\b i | |
Unit 5* Undead Death Knight 死亡騎士 Death Knight|DK i | |
Unit 5* Undead Bomb Unit 炸彈魔 Bomb Unit|Bomb i | |
Unit 5* Undead Hands of Death HoD 法老王 Hands of Death|Hand of Death|HoD i | |
Unit 5* Elf Sylphid 射擊遊戲 Sylphid|Sylph i | |
Unit 5* Elf Alchemist Alch 煉金術師 煉金 Alchemist|Alch i | |
Unit 5* Elf Hoyden Goku Goku 蠻橫女悟空 悟空 Hoyden Goku|Goku i | |
Unit 5* Elf Forest Guardian 大地守護神 Forest Guardian|FG i | |
Unit 5* Elf Druid 德魯伊 Druid i | |
Unit 5* Elf Fairy 妖精 Fairy i | |
Unit 5* Elf Unicorn Knight UK 精靈半人馬 半人馬 Unicorn Knight|UK i | |
Unit 5* Elf Wolf Warrior 狼戰士 Wolf Warrior|WW i | |
Unit 5* Elf Ent 樹精 Ent i | |
Unit 5* Human Steam Punk 蒸汽龐克 Steam ?Punk|\bSP\b i | |
Unit 5* Human Pilot 飛行員 Pilot i | |
Unit 5* Human Hot - Blooded Xuanzang HotBlooded 熱血三藏 三藏 Hot - Blooded Xuanzang|Hot ?Blood(ed)?|\bHB\b i | |
Unit 5* Human Griffin Rider Griffin 格裏芬 Griffin ?Rider|Griffin|\bGR\b i | |
Unit 5* Human Aladdin 阿拉丁 Aladdin i | |
Unit 5* Human Priest 人族祭司 Priest i | |
Unit 5* Human Gunner 人族砲兵 Gunner i | |
Unit 5* Human Fire Mage 人族火巫 Fire ?Mage|\bFM\b i | |
Unit 5* Human Golem 泰坦 Golem i | |
Unit 4* Orc Orc Ax Unit OA 獸族斧戰士 獸族斧戰士(飛斧) Orc Ax Unit|\bOAU?\b i | |
Unit 4* Undead Black Magic Wizard BMW 詛咒法師 Black Magic Wizard|\bBMW\b i | |
Unit 4* Elf Wind Mage WM 精靈風暴法師 Wind Mage|\bWM\b i | |
Unit 4* Human Cavalry Knight 人族騎士 Cavalry Knight|Cavalry|\bCK\b i | |
Unit 3* Orc Orc Hammer Unit OHU 獸族斧戰兵 獸族斧戰兵(嘲諷) Orc Hammer Unit|\bOHU\b i | |
Unit 3* Orc Orc Wing 獸族冰鳥 Orc Wing|\bOW\b i | |
Unit 3* Undead Great Hammer Unit GHU 骷髏戰鎚兵 Great Hammer Unit|\bGHU?\b i | |
Unit 3* Undead Skeleton Warrior SW 骷髏戰士 Skeleton Warrior|\bSW\b i | |
Unit 3* Elf Green Eagle 綠鷹 Green Eagle|Eagle|\bGE\b i | |
Unit 3* Elf High Elf Archer HEA 遠古精靈弓兵 High Elf Archer|\bHEA\b i | |
Unit 3* Human Hammer Knight HK 人族戰錘兵 Hammer Knight|\bHK\b i | |
Unit 3* Human Musketeer 人族槍兵 Musketeer|Musk i | |
Unit 2* Orc Frost Mage 獸族冰巫 Frost ?Mage|Frost i | |
Unit 2* Orc Orc Hunter OH 獸族獵人 Orc Hunter|\bOH\b i | |
Unit 2* Undead Warlock 電術士 Warlock i | |
Unit 2* Undead Ghost 幽鬼 Ghost i | |
Unit 2* Elf Poison Archer 毒弓兵 Poison Archer|\bPA\b i | |
Unit 2* Elf Elf Warrior 精靈戰士 Elf Warrior|\bEW\b i | |
Unit 2* Human Fire Bird 火鳥 Fire ?Bird|\bFB\b i | |
Unit 2* Human Heavy Infantry 人族重步兵 Heavy ?Inf(antry)?|\bHI\b i | |
Unit 1* Orc Orc Fighter 獸族戰士 [Oo]rc [Ff]ighter|\bOF\b i | |
Unit 1* Undead Skeleton Unit 骷髏兵 Skeleton Unit | |
Unit 1* Elf Elf Archer 精靈弓兵 Elf Archer|\bEA\b i | |
Unit 1* Human Infantry 人族步兵 Infantry|\bInf\b i | |
Modifier normal + i | |
Modifier S. 高階 \bS\. * i remove trailing space if needed | |
Modifier T${level} T${level} T(?<level>[1-3]) * i remove trailing space if needed | |
Modifier ${quantity}x ${quantity}x \b(?<quantity>\d)x + i | |
Modifier Luck 運氣 luck i | |
Modifier Speed 速度 speed i | |
??? all 全 all + i | |
??? rest 剩下全 rest + i | |
??? tank 坦 tank i | |
??? any 任意 any|filler i | |
??? or 或 \bor\b i | |
Formatting ", " ", " ",? *(and|then) +" i | |
Formatting ", " ", " ",(?! )" | |
Formatting "," "," " +," | |
Formatting + | |
Formatting \.$ | |
Formatting 1x ? |
This file contains 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
<Query Kind="Program"> | |
<Reference><RuntimeDirectory>\System.Net.Http.dll</Reference> | |
<NuGetReference>AngleSharp</NuGetReference> | |
<NuGetReference>LumenWorksCsvReader</NuGetReference> | |
<Namespace>AngleSharp</Namespace> | |
<Namespace>AngleSharp.Dom</Namespace> | |
<Namespace>AngleSharp.Html</Namespace> | |
<Namespace>AngleSharp.Parser.Html</Namespace> | |
<Namespace>System.Net.Http</Namespace> | |
<Namespace>System.Threading.Tasks</Namespace> | |
<Namespace>LumenWorks.Framework.IO.Csv</Namespace> | |
</Query> | |
void Main() | |
{ | |
Util.NewProcess = true; | |
const string SolutionTheadUrl = @"https://www.reddit.com/r/EndlessFrontier/comments/5bco5z/tower_of_trial_round_74/"; | |
var comments = Reddit.GetComments(SolutionTheadUrl); | |
comments | |
.Dump("raw comment data", 0) | |
.ApplyQuickFixes(GetQuickFixes().ToArray()) | |
.ApplyFixes(GetSolutionFixes()) | |
.RemoveNoSolutionTextOrComment(showRemovedContent: true) | |
.Dump("solutions", 0) | |
.AddCustomSolution(GetCustomSolution()) | |
.Cleanup() | |
.SingularizeUnitName() | |
.FormatEnglish() | |
//.TranslateToChinese() | |
.AggregateAndSortSolutions() | |
.OnDemand() | |
.Dump("sorted solutions"); | |
} | |
// Define other methods and classes here | |
/// <summary></summary> | |
string GetSolutionFixes() | |
{ | |
// note: delete fix cannot be placed last... | |
return @" | |
-----t1_exampl1@DEADBEEF----- | |
-----t1_exampl2@BAADF00D----- | |
note: the above fix will remove the referenced comment | |
you can edit the comment however you like here. | |
usually it is used to: | |
- voiding a solution, by removing the line or screwing up the syntax | |
- fix formatting issues like`Lich<sup>3</sup>` which is read as `Lich3` and then formatted to `Lich 3x`... | |
- fix other minor aesthetic issues like adding comma separator between units | |
" | |
.Trim(); | |
} | |
/// <summary>Insert additional solutions that are not found on the solution thread, like from Discord</summary> | |
string GetCustomSolution() | |
{ | |
return @" | |
99: all T3 SW | |
" | |
.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries) | |
.Where(x => !string.IsNullOrWhiteSpace(x)) | |
.Where(x => !x.StartsWith("//")) | |
.Aggregate("", (a, b) => a + '\n' + b) | |
.Trim(); | |
} | |
/// <summary>Used to fix simple error like typo, or repeated errors by the same person</summary> | |
/// <remarks>not every short name should be added into the "translation rules", at least not until it is widely accepted</remarks> | |
IEnumerable<Func<string, string>> GetQuickFixes() | |
{ | |
Func<string, string, Func<string, string>> replace = (a, b) => x => x.Replace(a, b); | |
Func<string, string, Func<string, string>> rgxreplace = (a, b) => x => Regex.Replace(x, a, b); | |
yield return replace("Lock", "Lich"); | |
yield return replace("cloak - ", ""); | |
yield return replace("s -> credit to asyranilin", ""); | |
yield return replace("Infantery", "Infantry"); | |
yield return rgxreplace("Sylp(hid)?", "Sylph"); | |
yield return replace("Punk", "SteamPunk"); | |
yield return replace("Griffon Rider", "GR"); | |
yield return replace("probably a boatload of luck", ""); | |
} | |
public class Reddit | |
{ | |
public static List<RedditComment> GetComments(string url) | |
{ | |
var html = new HttpClient().GetStringAsync(url).Result; | |
var document = new HtmlParser().Parse(html); | |
return document.QuerySelector(".sitetable.nestedlisting") | |
.QuerySelectorAll(".thing.comment") | |
.Select(RedditComment.Parse) | |
.ToList(); | |
} | |
} | |
public class RedditComment | |
{ | |
public string ID { get; set; } | |
//public string Author { get; set; } | |
public string Text { get; set; } | |
public string Hash { get; set; } | |
public Hyperlinq FixMe => new Hyperlinq(() => | |
$"\n-----{ID}@{Hash}-----\n{Text}" | |
.Dump($"fix for " + ID), | |
"FixMe"); | |
public static RedditComment Parse(IElement element) | |
{ | |
return new RedditComment | |
{ | |
ID = element.GetAttribute("data-fullname"), | |
//Author = element.GetAttribute("data-author"), | |
Text = element.QuerySelector(">.entry>form.usertext>.usertext-body").TextContent.Trim(), | |
Hash = element.QuerySelector(">.entry>form.usertext>.usertext-body").TextContent.Trim().GetHashCode().ToString("X") | |
}; | |
} | |
} | |
public class TranslationRule | |
{ | |
public string Chinese { get; set; } | |
public string English { get; set; } | |
public string RegexPattern { get; set; } | |
public RegexOptions RegexOptions { get; set; } | |
public string Translate(string input, Func<TranslationRule, string> languageSelector) | |
{ | |
return Regex.Replace(input, RegexPattern, languageSelector(this), RegexOptions); | |
} | |
} | |
public class Translator : List<TranslationRule> | |
{ | |
public static Translator Instance { get; } | |
static Translator() | |
{ | |
var path = Path.Combine( | |
Path.GetDirectoryName(Util.CurrentQueryPath), | |
"translation rules.txt"); | |
using (var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) | |
using (var stream = new StreamReader(file, Encoding.GetEncoding("GBK"))) | |
using (var csv = new CsvReader(stream, true, '\t', CsvReader.DefaultQuote, CsvReader.DefaultEscape, CsvReader.DefaultComment, ValueTrimmingOptions.None)) | |
{ | |
Func<string, string, string> coalesce = | |
(a, b) => string.IsNullOrWhiteSpace(a) ? b : a; | |
var regexOptions = new Dictionary<char, RegexOptions> | |
{ | |
['i'] = RegexOptions.IgnoreCase, | |
['m'] = RegexOptions.Multiline, | |
['s'] = RegexOptions.Singleline, | |
['n'] = RegexOptions.ExplicitCapture, | |
['x'] = RegexOptions.IgnorePatternWhitespace, | |
}; | |
Instance = new Translator(); | |
Instance.AddRange(csv.Select(x => new TranslationRule() | |
{ | |
English = coalesce(x[4], x[3]), | |
Chinese = coalesce(x[6], x[5]), | |
RegexPattern = x[7], | |
RegexOptions = x[8].ToCharArray() | |
.Select(c => regexOptions[c]) | |
.Aggregate(RegexOptions.None, (a, b) => a | b) | |
})); | |
} | |
} | |
public string Translate(string input, Func<TranslationRule, string> languageSelector) | |
{ | |
foreach (var rule in this.Where(x => !string.IsNullOrWhiteSpace(x.RegexPattern))) | |
{ | |
input = rule.Translate(input, languageSelector); | |
} | |
return input; | |
} | |
} | |
public static class CommentHelper | |
{ | |
/// <summary>Remove any line from the comment which doesnt abide the solution synax: {floor}[separators] {solution...} </summary> | |
public static IList<RedditComment> RemoveNoSolutionTextOrComment(this IList<RedditComment> comments, bool showRemovedContent = true) | |
{ | |
var removedTexts = Enumerable | |
.Repeat(new | |
{ | |
ID = default(string), | |
Deleted = default(bool), | |
Text = default(object), | |
FixMe = default(Hyperlinq), | |
}, 0) | |
.ToList(); | |
foreach (var comment in comments.ToList()) | |
{ | |
var lines = comment.Text.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries) | |
.Select(x => new | |
{ | |
Text = x, | |
Match = Regex.Match(x, @"^(floor ?)?(?<floor>\d+(\-\d+)?) ?[:\-f)] *(?<solution>.+)", RegexOptions.IgnoreCase) | |
}) | |
.ToList(); | |
#region Debug code | |
if (showRemovedContent && lines.Any(x => !x.Match.Success)) | |
{ | |
removedTexts.Add(new | |
{ | |
comment.ID, | |
Deleted = !lines.Any(x => x.Match.Success), | |
Text = Util.VerticalRun(lines.Select(line => | |
Util.HighlightIf(line.Text, _ => !line.Match.Success))), | |
FixMe = comment.FixMe, | |
}); | |
} | |
#endregion | |
if (!lines.Any(x => x.Match.Success)) | |
{ | |
comments.Remove(comment); | |
continue; | |
} | |
comment.Text = string.Join("\n", lines | |
.Where(x => x.Match.Success) | |
.Select(x => x.Match.Result("${floor}: ${solution}"))); | |
} | |
#region Debug code | |
if (showRemovedContent) | |
{ | |
removedTexts | |
.OnDemand() | |
.Dump("removed content"); | |
} | |
#endregion | |
return comments; | |
} | |
/// <summary>Fully replace or remove comment content. The hash is used to check is the content is invalidated by an edit and to throw an error should it happen.</summary> | |
/// <remarks>An empty fix without any content removes the referenced comment</remarks> | |
public static IList<RedditComment> ApplyFixes(this IList<RedditComment> comments, string solutionFixes) | |
{ | |
var fixes = Regex.Split(solutionFixes, "(?=-----.+@.+-----)") | |
.Skip(1) | |
.Select(text => text.Split('\n')) | |
.Select(lines => new | |
{ | |
CommentID = Regex.Match(lines[0], "^-----(?<id>.+)@(?<hash>.+)-----\r$").Result("${id}"), | |
CommentHash = Regex.Match(lines[0], "^-----(?<id>.+)@(?<hash>.+)-----\r$").Result("${hash}"), | |
UpdatedCommentText = string.Join("\n", lines.Skip(1)) | |
}); | |
foreach (var fix in fixes) | |
{ | |
var comment = comments.FirstOrDefault(x => x.ID == fix.CommentID); | |
if (comment == null) continue; | |
if (comment.Hash != fix.CommentHash) | |
{ | |
comment.FixMe.Action.Invoke(); | |
throw new InvalidDataException("solution has been updated"); | |
} | |
if (string.IsNullOrWhiteSpace(fix.UpdatedCommentText)) | |
{ | |
comments.Remove(comment); | |
continue; | |
} | |
comment.Text = fix.UpdatedCommentText; | |
} | |
return comments; | |
} | |
/// <summary>Apply simple text substitution <see cref="GetQuickFixes()"></summary> | |
public static IList<RedditComment> ApplyQuickFixes(this IList<RedditComment> comments, params Func<string, string>[] solutionFixes) | |
{ | |
foreach (var comment in comments) | |
{ | |
comment.Text = solutionFixes.Aggregate(comment.Text, (text, fix) => fix(text)); | |
} | |
return comments; | |
} | |
/// <summary>Change plural form to singular form</summary> | |
/// <remarks>for repeated offender mustly</remarks> | |
public static IList<RedditComment> SingularizeUnitName(this IList<RedditComment> comments) | |
{ | |
foreach (var comment in comments) | |
{ | |
comment.Text = Regex.Replace( | |
comment.Text, | |
"(?<noun>mage|warrior|knight|musketeer)s", | |
"${noun}", | |
RegexOptions.IgnoreCase); | |
} | |
return comments; | |
} | |
/// <summary>Remove parenthesis and anything in between</summary> | |
public static IList<RedditComment> Cleanup(this IList<RedditComment> comments) | |
{ | |
foreach (var comment in comments) | |
{ | |
comment.Text = Regex.Replace(comment.Text, @"\(.+\)", ""); | |
} | |
return comments; | |
} | |
public static IList<RedditComment> AddCustomSolution(this IList<RedditComment> comments, string solution) | |
{ | |
if (!string.IsNullOrWhiteSpace(solution)) | |
{ | |
comments.Add(new RedditComment() | |
{ | |
Text = solution | |
}); | |
} | |
return comments; | |
} | |
public static IList<RedditComment> FormatEnglish(this IList<RedditComment> comments) | |
{ | |
foreach (var comment in comments) | |
{ | |
comment.Text = Translator.Instance.Translate(comment.Text, r => r.English); | |
} | |
return comments; | |
} | |
/// <remarks>This is also used to quickly validate all input are sanitized, as any no-formatted part is contrasted with chinese</remarks> | |
public static IList<RedditComment> TranslateToChinese(this IList<RedditComment> comments) | |
{ | |
foreach (var comment in comments) | |
{ | |
comment.Text = Translator.Instance.Translate(comment.Text, r => r.Chinese); | |
} | |
return comments; | |
} | |
/// <summary>Convert the comments into a list of solution</summary> | |
public static string AggregateAndSortSolutions(this IList<RedditComment> comments) | |
{ | |
const string MarkUpNewLine = " "; | |
return string.Join("\n", comments | |
.SelectMany(x => x.Text.Split('\n')) | |
.Select(x => x.Trim() + MarkUpNewLine) | |
.Distinct() | |
.OrderBy(x => int.Parse(Regex.Match(x, @"^\d+").Value))); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment