lorcana-deck
v1.0.3
Published
Terminal deck builder for Disney Lorcana with ASCII card art
Maintainers
Readme
lorcana-deck-builder
A terminal deck builder for Disney Lorcana, with ASCII card art, live search and deck-legality checks. No dependencies, just Node 18+.
LORCANA deck builder deck aggro-ruby · 60/60 legal
/ ink:ruby cost<=3 │ amb ame eme RUB sap ste │ Core │ 84 matches
BROWSE 84 tab → deck │╭──────────────────────────────────────╮
▸4x ⬡ 2 ◆ Beast - Aggressi… Ruby ││ ⬡ 2 ◆ Beast │
4x ⬡ 1 ◆ Dangerous Plan Ruby ││ Aggressive Lord │
· ⬡ 3 ◆ Fidget - Ratigan… Ruby │├──────────────────────────────────────┤
││ Character · Storyborn · Hero · │
││ Prince · Whisper │
│├──────────────────────────────────────┤
││ Boost 2 ⬡ (Once during your turn, │
││ you may pay 2 ⬡ to put the top │
││ card of your deck facedown under │
││ this character.) │
│├──────────────────────────────────────┤
││ ¤ 3 ♥ 2 ◊ 1 │
││ Ruby · Uncommon · set 10 │
│╰──────────────────────────────────────╯Running it
npx lorcana-deck # no install, just run itOr install it for good:
npm install -g lorcana-deck # gives you `lorcana-deck` and `lorcana`From a checkout:
node bin/lorcana.js # open the builder
npm link # installs `lorcana` globally from this working copyCard data
Card data comes from LorcanaJSON (community-maintained,
Ravensburger card images and text). It downloads once to ~/.lorcana-deck/cards.json on first
run (about 9 MB, ~2 500 unique cards), and everything after that works offline.
lorcana update # re-download when a new set dropsKeys
| key | action |
| --- | --- |
| ↑ ↓ / j k | move the cursor |
| PgUp PgDn | jump a page |
| → / space / + | add a copy to the deck |
| ← / backspace | remove a copy |
| D | remove all copies |
| tab | switch between the browse list and the deck list |
| / | search, filters as you type |
| 1 … 6 | toggle an ink filter (Amber, Amethyst, Emerald, Ruby, Sapphire, Steel) |
| t | cycle the card type filter |
| i | show inkable cards only |
| f | cycle format: Core / Infinity / any |
| r | cycle sort: cost / name / lore / power |
| p | show the selected card full size |
| n s o | new deck / save / open |
| e | export the deck as a text list |
| c | clear the deck |
| ? | help |
| q | quit |
Search
The search box takes plain words plus filters, combined with spaces. Prefix any
term with - to exclude it.
ink:ruby cost<=3 kw:evasive cheap evasive Ruby cards
type:character sub:princess princesses
lore>=3 -ink:amber big questers outside Amber
text:"draw a card" cost<=2 cheap draw| filter | matches |
| --- | --- |
| ink: i: | ink colour (dual-ink cards match either half) |
| type: t: | Character, Action, Item, Location |
| sub: | classifications: Hero, Villain, Princess, Floodborn… |
| kw: k: | keywords: Evasive, Rush, Shift, Bodyguard… |
| cost: lore: str: will: | numbers, with < <= > >= = |
| rarity: set: story: | printing details |
| text: x: | rules text |
| inkwell:yes / inkwell:no | whether the card can be inked |
Deck rules it checks
- 60 cards minimum
- at most 4 copies of a card (cards with their own limit, like The Glass Slipper at 2 and Dalmatian Puppy - Tail Wagger at 99, use theirs)
- at most 2 inks, counting both halves of a dual-ink card
- every card legal in the selected format
It also nags about soft stuff: under 45% inkable, or an average cost above 4.
Importing decks
Paste a list from Dreamborn, inkdecks or anywhere else. Either give it a file or pipe it in:
lorcana import mydeck.txt # deck is named after the file
pbpaste | lorcana import - # straight from the clipboard
pbpaste | lorcana import - --name azurite # ... with a name you pickName matching ignores case, accents, apostrophes and punctuation, and the -
between a character and their version is optional, so all of these find the same
card:
4 Elsa - Spirit of Winter
4 elsa spirit of winter
4x ELSA - SPIRIT OF WINTERThe parser also copes with the decoration real exports carry: 4x or x4
quantities, - and * bullets, section headers like Characters (18), tabs,
Windows line endings, # and // comments, and set annotations such as
(TFC) 1, (1/204) or a leading [TFC-1]. Duplicate lines for one card are
added together and capped at that card's play limit.
Anything it cannot match is reported with the closest names it knows, so a typo takes one look to fix:
skipped "Ursula Deciever" did you mean: Ursula - Deceiver | Ursula - Vanessa | Ursula - Sea WitchAfter importing it prints the same legality check the builder uses, which is usually how you find out a list was for a different format:
Imported 60 cards → ~/.lorcana-deck/decks/azurite.json
! Te Kā - The Burning One is not Core-legalSimulating games
lorcana sim plays your deck out against other decks and reports how it did.
lorcana sim queenbehavior # mirror match, 500 games
lorcana sim queenbehavior --vs sparring-ruby-steel-aggro
lorcana sim queenbehavior --vs all --games 2000 # every other saved deck
lorcana sim queenbehavior --vs aggro --trace # replay one game, action by action queenbehavior 400 games per matchup fidelity 100.0%
sparring-ruby-steel-aggro █████░░░░░░░░░░ 26.5% +/-4.3 fidelity 100.0%
106W 294L avg turns 8.6 ◊ 12.6 vs 17.0 first-player edge 57.0%--games defaults to 500 and runs at roughly 3 500 games a second, so a big
gauntlet costs a second or two. --seed makes a run reproducible. Who goes first
alternates every game, and first-player edge reports how much that mattered: a
number near 50% means the matchup is interactive, while 75%+ means both decks are
mostly ignoring each other and racing.
Opponent decks are just saved decks, so paste a list from Dreamborn or inkdecks
into a file and lorcana import it. With no --vs it plays the mirror, which
needs nothing extra. The repository also carries two synthetic sparring decks in
decks/ - generated filler rather than real meta lists, so replace them with the
real thing when you care about the answer.
Fidelity, and why it is printed everywhere
Card data from LorcanaJSON gives an ability's type but its effect as prose, so
the engine cannot read what a card does. Abilities are hand-written scripts in
src/sim/scripts.js, keyed by card name:
// When you play this character, you may play a character with cost 1 for free.
register('Pocahontas - Guiding the Tribe', {
onPlay: ctx => ctx.playFreeFromHand(card => isCharacter(card) && card.cost === 1),
})Every script sits under the printed text it implements, so a misread card is easy
to spot. A card with no script still plays as a body with its keywords, which means
results stay runnable but stop being true. fidelity is the share of the deck
the engine can play properly, and any deck below 90% says so on every line. The
report also lists exactly which cards it is faking, so a suspicious number is
traceable to the cards that caused it.
The 22 keywords split the same way: Evasive, Rush, Bodyguard, Support, Ward, Resist, Challenger, Singer, Sing Together, Reckless, Alert, Vanish and Shift are enforced by the engine; Boost and the one-off Shift variants are not, and count against fidelity.
What the bot is and is not
The bot inks on a curve, plays the most it can afford, quests with everything that gains lore, and challenges only when the lore maths favours it - denying a quester is weighed against the quest it skips and the body it might lose, and it trades more readily when it is behind. That is about beginner-to-decent play. It does not bluff, hold blockers, plan two turns out, or know what your deck is trying to do.
So a 35% or a 65% is telling you something structural. A 51% is telling you nothing, which is why the +/- margin is printed next to it.
Other commands
lorcana search "ink:sapphire kw:ward" # print matching cards
lorcana show "Elsa - Spirit of Winter" # print one card
lorcana decks # list saved decks
lorcana open aggro-ruby # open the builder on a saved deck
lorcana stats aggro-ruby # curve, inks, warnings
lorcana export aggro-ruby # text list, one "4 Card Name" per line
lorcana import list.txt # build a deck from that format
lorcana sim aggro-ruby --vs all # simulate against every saved deckDecks are plain JSON in ~/.lorcana-deck/decks/ (override with LORCANA_HOME). The text format is the usual 4 Card Name list
that Dreamborn and friends use, so decks move in and out easily. See
Importing decks for everything the parser tolerates.
Flags
--ascii swaps the unicode glyphs and box drawing for plain ASCII.
--no-color (or NO_COLOR=1) turns off colour.
