diff --git a/data/.github/workflows/update-champions.yml b/data/.github/workflows/update-champions.yml
index 8131ef0..1f5cab9 100644
--- a/data/.github/workflows/update-champions.yml
+++ b/data/.github/workflows/update-champions.yml
@@ -32,7 +32,7 @@ jobs:
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- git add champions.json
+ git add champions.json CHAMPIONS.md
if git diff --cached --quiet; then
echo "No changes to commit"
exit 0
diff --git a/data/CHAMPIONS.md b/data/CHAMPIONS.md
index 14df806..2e34e4c 100644
--- a/data/CHAMPIONS.md
+++ b/data/CHAMPIONS.md
@@ -17,11 +17,11 @@ Columns match Loldle classic mode: Champion | Gender | Genre (class) | Range | R
|
| **Annie**
the Dark Child | Female | Mage, Support | Ranged | Mana | Runeterra | Mid | 2009 |
|
| **Aphelios**
the Weapon of the Faithful | Male | Marksman | Ranged | Mana | Mount Targon | Bottom | 2019 |
|
| **Ashe**
the Frost Archer | Female | Marksman, Support | Ranged | Mana | Freljord | Bottom, Support | 2009 |
-|
| **Aurelion Sol**
The Star Forger | Male | Mage | Ranged | Mana | Mount Targon | Mid | 2016 |
+|
| **Aurelion Sol**
The Star Forger | Male | Mage | Ranged | Mana | Mount Targon | Mid | 2016 |
|
| **Aurora**
the Witch Between Worlds | Female | Mage, Assassin | Ranged | Mana | Freljord | Top, Mid | 2024 |
|
| **Azir**
the Emperor of the Sands | Male | Mage, Marksman | Ranged | Mana | Shurima | Mid | 2014 |
|
| **Bard**
the Wandering Caretaker | Male | Support, Mage | Ranged | Mana | Runeterra | Support | 2015 |
-|
| **Bel'Veth**
the Empress of the Void | Female | Fighter | Melee | — | Void | Jungle | 2022 |
+|
| **Bel'Veth**
the Empress of the Void | Female | Fighter | Melee | — | Void | Jungle | 2022 |
|
| **Blitzcrank**
the Great Steam Golem | Male | Tank, Support | Melee | Mana | Zaun | Support | 2009 |
|
| **Brand**
the Burning Vengeance | Male | Mage, Support | Ranged | Mana | Runeterra | Jungle, Support | 2011 |
|
| **Braum**
the Heart of the Freljord | Male | Tank, Support | Melee | Mana | Freljord | Support | 2014 |
@@ -29,11 +29,11 @@ Columns match Loldle classic mode: Champion | Gender | Genre (class) | Range | R
|
| **Caitlyn**
the Sheriff of Piltover | Female | Marksman | Ranged | Mana | Piltover | Bottom | 2011 |
|
| **Camille**
the Steel Shadow | Female | Fighter, Assassin | Melee | Mana | Piltover | Top | 2016 |
|
| **Cassiopeia**
the Serpent's Embrace | Female | Mage | Ranged | Mana | Noxus | Mid | 2010 |
-|
| **Cho'Gath**
the Terror of the Void | Divers | Tank, Mage | Melee | Mana | Void | Top | 2009 |
+|
| **Cho'Gath**
the Terror of the Void | Divers | Tank, Mage | Melee | Mana | Void | Top | 2009 |
|
| **Corki**
the Daring Bombardier | Male | Marksman, Mage | Ranged | Mana | Bandle City | Mid | 2009 |
|
| **Darius**
the Hand of Noxus | Male | Fighter, Tank | Melee | Mana | Noxus | Top | 2012 |
|
| **Diana**
Scorn of the Moon | Female | Fighter, Assassin | Melee | Mana | Mount Targon | Jungle, Mid | 2012 |
-|
| **Dr. Mundo**
the Madman of Zaun | Male | Tank, Fighter | Melee | None | Zaun | Top | 2009 |
+|
| **Dr. Mundo**
the Madman of Zaun | Male | Tank, Fighter | Melee | None | Zaun | Top | 2009 |
|
| **Draven**
the Glorious Executioner | Male | Marksman | Ranged | Mana | Noxus | Bottom | 2012 |
|
| **Ekko**
the Boy Who Shattered Time | Male | Assassin, Mage | Melee | Mana | Zaun | Jungle, Mid | 2015 |
|
| **Elise**
the Spider Queen | Female | Assassin, Mage | Ranged | Mana | Shadow Isles | Jungle | 2012 |
@@ -56,13 +56,13 @@ Columns match Loldle classic mode: Champion | Gender | Genre (class) | Range | R
|
| **Irelia**
the Blade Dancer | Female | Fighter, Assassin | Melee | Mana | Ionia | Top, Mid | 2010 |
|
| **Ivern**
the Green Father | Male | Support, Mage | Melee | Mana | Ionia | Jungle | 2016 |
|
| **Janna**
the Storm's Fury | Female | Support, Mage | Ranged | Mana | Zaun | Support | 2009 |
-|
| **Jarvan IV**
the Exemplar of Demacia | Male | Fighter, Tank | Melee | Mana | Demacia | Jungle | 2011 |
+|
| **Jarvan IV**
the Exemplar of Demacia | Male | Fighter, Tank | Melee | Mana | Demacia | Jungle | 2011 |
|
| **Jax**
Grandmaster at Arms | Male | Fighter | Melee | Mana | Runeterra | Top, Jungle | 2009 |
|
| **Jayce**
the Defender of Tomorrow | Male | Fighter, Marksman | Melee | Mana | Piltover | Top | 2012 |
|
| **Jhin**
the Virtuoso | Male | Marksman, Mage | Ranged | Mana | Ionia | Bottom | 2016 |
|
| **Jinx**
the Loose Cannon | Female | Marksman | Ranged | Mana | Zaun | Bottom | 2013 |
-|
| **K'Sante**
the Pride of Nazumah | Male | Tank, Fighter | Melee | Mana | Shurima | Top | 2022 |
-|
| **Kai'Sa**
Daughter of the Void | Female | Marksman, Mage | Ranged | Mana | Void | Bottom | 2018 |
+|
| **K'Sante**
the Pride of Nazumah | Male | Tank, Fighter | Melee | Mana | Shurima | Top | 2022 |
+|
| **Kai'Sa**
Daughter of the Void | Female | Marksman, Mage | Ranged | Mana | Void | Bottom | 2018 |
|
| **Kalista**
the Spear of Vengeance | Female | Marksman | Ranged | Mana | Shadow Isles | Bottom | 2014 |
|
| **Karma**
the Enlightened One | Female | Mage, Support | Ranged | Mana | Ionia | Mid, Support | 2011 |
|
| **Karthus**
the Deathsinger | Male | Mage | Melee | Mana | Shadow Isles | Jungle | 2009 |
@@ -71,12 +71,12 @@ Columns match Loldle classic mode: Champion | Gender | Genre (class) | Range | R
|
| **Kayle**
the Righteous | Female | Mage, Marksman | Melee | Mana | Demacia | Top | 2009 |
|
| **Kayn**
the Shadow Reaper | Male | Fighter, Assassin | Melee | Mana | Ionia | Jungle | 2017 |
|
| **Kennen**
the Heart of the Tempest | Male | Mage | Ranged | Energy | Ionia | Top | 2010 |
-|
| **Kha'Zix**
the Voidreaver | Male | Assassin | Melee | Mana | Void | Jungle | 2012 |
+|
| **Kha'Zix**
the Voidreaver | Male | Assassin | Melee | Mana | Void | Jungle | 2012 |
|
| **Kindred**
The Eternal Hunters | Male | Marksman | Ranged | Mana | Runeterra | Jungle | 2015 |
|
| **Kled**
the Cantankerous Cavalier | Male | Fighter | Melee | Courage | Noxus | Top | 2016 |
-|
| **Kog'Maw**
the Mouth of the Abyss | Male | Marksman, Mage | Ranged | Mana | Void | Bottom | 2010 |
-|
| **LeBlanc**
the Deceiver | Female | Assassin, Mage | Ranged | Mana | Noxus | Mid | 2010 |
-|
| **Lee Sin**
the Blind Monk | Male | Fighter, Assassin | Melee | Energy | Ionia | Jungle | 2011 |
+|
| **Kog'Maw**
the Mouth of the Abyss | Male | Marksman, Mage | Ranged | Mana | Void | Bottom | 2010 |
+|
| **LeBlanc**
the Deceiver | Female | Assassin, Mage | Ranged | Mana | Noxus | Mid | 2010 |
+|
| **Lee Sin**
the Blind Monk | Male | Fighter, Assassin | Melee | Energy | Ionia | Jungle | 2011 |
|
| **Leona**
the Radiant Dawn | Female | Tank, Support | Melee | Mana | Mount Targon | Support | 2011 |
|
| **Lillia**
the Bashful Bloom | Female | Fighter, Mage | Melee | Mana | Ionia | Jungle | 2020 |
|
| **Lissandra**
the Ice Witch | Female | Mage | Ranged | Mana | Freljord | Mid | 2013 |
@@ -86,10 +86,10 @@ Columns match Loldle classic mode: Champion | Gender | Genre (class) | Range | R
|
| **Malphite**
Shard of the Monolith | Male | Tank, Mage | Melee | Mana | Ixtal | Top | 2009 |
|
| **Malzahar**
the Prophet of the Void | Male | Mage | Ranged | Mana | Void | Mid | 2010 |
|
| **Maokai**
the Twisted Treant | Male | Tank, Support | Melee | Mana | Shadow Isles | Support | 2011 |
-|
| **Master Yi**
the Wuju Bladesman | Male | Fighter, Assassin | Melee | Mana | Ionia | Jungle | 2009 |
+|
| **Master Yi**
the Wuju Bladesman | Male | Fighter, Assassin | Melee | Mana | Ionia | Jungle | 2009 |
|
| **Mel**
the Soul's Reflection | Female | Mage, Support | Ranged | Mana | Noxus | Mid | 2025 |
|
| **Milio**
The Gentle Flame | Male | Support, Mage | Ranged | Mana | Ixtal | Support | 2023 |
-|
| **Miss Fortune**
the Bounty Hunter | Female | Marksman, Mage | Ranged | Mana | Bilgewater | Bottom | 2010 |
+|
| **Miss Fortune**
the Bounty Hunter | Female | Marksman, Mage | Ranged | Mana | Bilgewater | Bottom | 2010 |
|
| **Mordekaiser**
the Iron Revenant | Male | Fighter, Mage | Melee | Shield | Noxus | Top | 2010 |
|
| **Morgana**
the Fallen | Female | Support, Mage | Melee | Mana | Demacia | Support | 2009 |
|
| **Naafiri**
the Hound of a Hundred Bites | Female | Assassin, Fighter | Melee | Mana | Shurima | Mid | 2023 |
@@ -100,7 +100,7 @@ Columns match Loldle classic mode: Champion | Gender | Genre (class) | Range | R
|
| **Nidalee**
the Bestial Huntress | Female | Assassin, Mage | Ranged | Mana | Ixtal | Jungle | 2009 |
|
| **Nilah**
the Joy Unbound | Female | Fighter, Assassin | Melee | Mana | Runeterra | Bottom | 2022 |
|
| **Nocturne**
the Eternal Nightmare | Divers | Fighter, Assassin | Melee | Mana | Runeterra | Jungle | 2011 |
-|
| **Nunu & Willump**
the Boy and His Yeti | Male | Tank, Mage | Melee | Mana | Freljord | Jungle | 2009 |
+|
| **Nunu & Willump**
the Boy and His Yeti | Male | Tank, Mage | Melee | Mana | Freljord | Jungle | 2009 |
|
| **Olaf**
the Berserker | Male | Fighter, Tank | Melee | Mana | Freljord | Top | 2010 |
|
| **Orianna**
the Lady of Clockwork | Female | Mage, Support | Ranged | Mana | Piltover | Mid | 2011 |
|
| **Ornn**
The Fire below the Mountain | Male | Tank | Melee | Mana | Freljord | Top | 2017 |
@@ -111,9 +111,9 @@ Columns match Loldle classic mode: Champion | Gender | Genre (class) | Range | R
|
| **Quinn**
Demacia's Wings | Female | Marksman, Assassin | Ranged | Mana | Demacia | Top | 2013 |
|
| **Rakan**
The Charmer | Male | Support | Melee | Mana | Ionia | Support | 2017 |
|
| **Rammus**
the Armordillo | Male | Tank | Melee | Mana | Shurima | Jungle | 2009 |
-|
| **Rek'Sai**
the Void Burrower | Female | Fighter, Tank | Melee | Rage | Void | Jungle | 2014 |
+|
| **Rek'Sai**
the Void Burrower | Female | Fighter, Tank | Melee | Rage | Void | Jungle | 2014 |
|
| **Rell**
the Iron Maiden | Female | Tank, Support | Melee | Mana | Noxus | Support | 2020 |
-|
| **Renata Glasc**
the Chem-Baroness | Female | Support, Mage | Ranged | Mana | Zaun | Support | 2022 |
+|
| **Renata Glasc**
the Chem-Baroness | Female | Support, Mage | Ranged | Mana | Zaun | Support | 2022 |
|
| **Renekton**
the Butcher of the Sands | Male | Fighter, Tank | Melee | Fury | Shurima | Top | 2011 |
|
| **Rengar**
the Pridestalker | Male | Assassin, Fighter | Melee | Ferocity | Ixtal | Jungle | 2012 |
|
| **Riven**
the Exile | Female | Fighter, Assassin | Melee | None | Noxus | Top | 2011 |
@@ -137,7 +137,7 @@ Columns match Loldle classic mode: Champion | Gender | Genre (class) | Range | R
|
| **Swain**
the Noxian Grand General | Male | Mage, Support | Ranged | Mana | Noxus | Support | 2010 |
|
| **Sylas**
the Unshackled | Male | Mage, Assassin | Melee | Mana | Demacia | Mid | 2019 |
|
| **Syndra**
the Dark Sovereign | Female | Mage | Ranged | Mana | Ionia | Mid | 2012 |
-|
| **Tahm Kench**
The River King | Male | Tank, Support | Melee | Mana | Bilgewater | Top | 2015 |
+|
| **Tahm Kench**
The River King | Male | Tank, Support | Melee | Mana | Bilgewater | Top | 2015 |
|
| **Taliyah**
the Stoneweaver | Female | Mage, Support | Ranged | Mana | Shurima | Jungle | 2016 |
|
| **Talon**
the Blade's Shadow | Male | Assassin | Melee | Mana | Noxus | Mid | 2011 |
|
| **Taric**
the Shield of Valoran | Male | Support, Tank | Melee | Mana | Mount Targon | Support | 2009 |
@@ -146,14 +146,14 @@ Columns match Loldle classic mode: Champion | Gender | Genre (class) | Range | R
|
| **Tristana**
the Yordle Gunner | Female | Marksman, Assassin | Ranged | Mana | Bandle City | Bottom | 2009 |
|
| **Trundle**
the Troll King | Male | Fighter, Tank | Melee | Mana | Freljord | Top | 2010 |
|
| **Tryndamere**
the Barbarian King | Male | Fighter, Assassin | Melee | Fury | Freljord | Top | 2009 |
-|
| **Twisted Fate**
the Card Master | Male | Mage, Marksman | Ranged | Mana | Bilgewater | Mid, Bottom | 2009 |
+|
| **Twisted Fate**
the Card Master | Male | Mage, Marksman | Ranged | Mana | Bilgewater | Mid, Bottom | 2009 |
|
| **Twitch**
the Plague Rat | Male | Marksman, Assassin | Ranged | Mana | Zaun | Bottom | 2009 |
|
| **Udyr**
the Spirit Walker | Male | Fighter, Tank | Melee | Mana | Freljord | Top, Jungle | 2009 |
|
| **Urgot**
the Dreadnought | Male | Fighter, Tank | Melee | Mana | Zaun | Top | 2010 |
|
| **Varus**
the Arrow of Retribution | Male | Marksman, Mage | Ranged | Mana | Ionia | Bottom | 2012 |
|
| **Vayne**
the Night Hunter | Female | Marksman, Assassin | Ranged | Mana | Demacia | Bottom | 2011 |
|
| **Veigar**
the Tiny Master of Evil | Male | Mage | Ranged | Mana | Bandle City | Mid | 2009 |
-|
| **Vel'Koz**
the Eye of the Void | Male | Mage, Support | Ranged | Mana | Void | Support | 2014 |
+|
| **Vel'Koz**
the Eye of the Void | Male | Mage, Support | Ranged | Mana | Void | Support | 2014 |
|
| **Vex**
the Gloomist | Female | Mage | Ranged | Mana | Shadow Isles | Mid | 2021 |
|
| **Vi**
the Piltover Enforcer | Female | Fighter, Assassin | Melee | Mana | Piltover | Jungle | 2012 |
|
| **Viego**
The Ruined King | Male | Fighter, Assassin | Melee | None | Shadow Isles | Jungle | 2021 |
@@ -161,10 +161,10 @@ Columns match Loldle classic mode: Champion | Gender | Genre (class) | Range | R
|
| **Vladimir**
the Crimson Reaper | Male | Mage, Fighter | Melee | Crimson Rush | Noxus | Mid | 2010 |
|
| **Volibear**
the Relentless Storm | Male | Fighter, Tank | Melee | Mana | Freljord | Top | 2011 |
|
| **Warwick**
the Uncaged Wrath of Zaun | Male | Fighter, Tank | Melee | Mana | Zaun | Jungle | 2009 |
-|
| **Wukong**
the Monkey King | Male | Fighter, Tank | Melee | Mana | Ionia | Jungle | 2011 |
+|
| **Wukong**
the Monkey King | Male | Fighter, Tank | Melee | Mana | Ionia | Jungle | 2011 |
|
| **Xayah**
the Rebel | Female | Marksman | Ranged | Mana | Ionia | Bottom | 2017 |
|
| **Xerath**
the Magus Ascendant | Male | Mage, Support | Ranged | Mana | Shurima | Mid, Support | 2011 |
-|
| **Xin Zhao**
the Seneschal of Demacia | Male | Fighter, Tank | Melee | Mana | Demacia | Jungle | 2010 |
+|
| **Xin Zhao**
the Seneschal of Demacia | Male | Fighter, Tank | Melee | Mana | Demacia | Jungle | 2010 |
|
| **Yasuo**
the Unforgiven | Male | Fighter, Assassin | Melee | Flow | Ionia | Top, Mid, Bottom | 2013 |
|
| **Yone**
the Unforgotten | Male | Fighter, Assassin | Melee | Flow | Ionia | Top, Mid | 2020 |
|
| **Yorick**
Shepherd of Souls | Male | Fighter, Tank | Melee | Mana | Shadow Isles | Top | 2011 |
diff --git a/data/main.go b/data/main.go
index b3e638e..abb4988 100644
--- a/data/main.go
+++ b/data/main.go
@@ -45,8 +45,13 @@ func main() {
if err := saveJSON(outputPath, champions); err != nil {
log.Fatalf("Failed to save JSON: %v", err)
}
-
fmt.Printf("Champion data has been saved to %s\n", outputPath)
+
+ markdownPath := "CHAMPIONS.md"
+ if err := saveMarkdown(markdownPath, champions); err != nil {
+ log.Fatalf("Failed to save Markdown: %v", err)
+ }
+ fmt.Printf("Champion table has been saved to %s\n", markdownPath)
}
func saveJSON(path string, data []parser.ChampionResult) error {
diff --git a/data/markdown.go b/data/markdown.go
new file mode 100644
index 0000000..f7cf8ee
--- /dev/null
+++ b/data/markdown.go
@@ -0,0 +1,113 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/tiennm99/loldle-data/parser"
+)
+
+// saveMarkdown renders champions as a human-readable table matching the
+// Loldle classic-mode column order: Champion | Gender | Genre | Range |
+// Resource | Region | Lane | Year.
+func saveMarkdown(path string, champions []parser.ChampionResult) error {
+ var b strings.Builder
+
+ fmt.Fprintln(&b, "# Champions")
+ fmt.Fprintln(&b)
+ fmt.Fprintf(&b, "Auto-generated from [`champions.json`](./champions.json). Total: **%d** champions.\n", len(champions))
+ fmt.Fprintln(&b)
+ fmt.Fprintln(&b, "Columns match Loldle classic mode: Champion | Gender | Genre (class) | Range | Resource | Region | Lane | Year.")
+ fmt.Fprintln(&b)
+ fmt.Fprintln(&b, "| | Champion | Gender | Genre | Range | Resource | Region | Lane | Year |")
+ fmt.Fprintln(&b, "|---|---|---|---|---|---|---|---|---|")
+
+ for _, c := range champions {
+ year := "—"
+ if c.ReleaseDate != 0 {
+ year = fmt.Sprintf("%d", c.ReleaseDate)
+ }
+ fmt.Fprintf(&b,
+ "| %s | **%s**
%s | %s | %s | %s | %s | %s | %s | %s |\n",
+ tileIcon(c.ID, c.Name),
+ c.Name,
+ c.Title,
+ formatGender(c.Gender),
+ formatMulti(c.Genre),
+ formatRange(c.AttackType),
+ orDash(c.Resource),
+ formatRegion(c.Region),
+ formatMulti(c.Lane),
+ year,
+ )
+ }
+
+ return os.WriteFile(path, []byte(b.String()), 0o644)
+}
+
+func tileIcon(id, alt string) string {
+ return fmt.Sprintf(
+ `
`,
+ id, alt,
+ )
+}
+
+func formatGender(v string) string { return orDash(capitalize(v)) }
+
+func formatRange(v string) string {
+ switch v {
+ case "close":
+ return "Melee"
+ case "range":
+ return "Ranged"
+ default:
+ return "—"
+ }
+}
+
+// formatMulti splits comma-separated values, trims, capitalizes each, rejoins with ", ".
+func formatMulti(v string) string {
+ if v == "" {
+ return "—"
+ }
+ parts := strings.Split(v, ",")
+ out := make([]string, 0, len(parts))
+ for _, p := range parts {
+ p = strings.TrimSpace(p)
+ if p == "" {
+ continue
+ }
+ out = append(out, capitalize(p))
+ }
+ if len(out) == 0 {
+ return "—"
+ }
+ return strings.Join(out, ", ")
+}
+
+// formatRegion splits dash-separated regions (e.g. "shadow-isles") and title-cases each.
+func formatRegion(v string) string {
+ if v == "" {
+ return "—"
+ }
+ parts := strings.Split(v, "-")
+ for i, p := range parts {
+ parts[i] = capitalize(p)
+ }
+ return strings.Join(parts, " ")
+}
+
+func capitalize(s string) string {
+ if s == "" {
+ return s
+ }
+ return strings.ToUpper(s[:1]) + s[1:]
+}
+
+func orDash(s string) string {
+ if s == "" {
+ return "—"
+ }
+ return s
+}