#!/usr/bin/env python3 """Find mechanically similar cards. python3 tools/similar.py --deck decks/reaper-king-no-more "Card Name" [-n 15] """ import sys, os, json, numpy as np sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from deckctx import DeckContext, ROOT def load(): z = np.load(os.path.join(ROOT, "data", "embeddings.npz"), allow_pickle=True) return list(z["names"]), z["embedding"] def main(): ctx = DeckContext.from_args() args = sys.argv[1:] if not args: sys.exit(__doc__) card = args[0] k = int(args[args.index("-n")+1]) if "-n" in args else 15 names, emb = load() idx = {n: i for i, n in enumerate(names)} if card not in idx: near = [n for n in names if card.lower() in n.lower()][:5] sys.exit(f"not found: {card}" + (f"\ndid you mean: {near}" if near else "")) # color-identity + legality filter from the oracle bulk, derived from # this deck's own commander -- never a hardcoded color combination from cards import DB db = DB(blacklist_path=ctx.blacklist_path) identity = ctx.identity(db) ci, legal = {}, {} for l in open(os.path.join(ROOT, "data", "oracle-cards.jsonl")): c = json.loads(l) ci[c["name"]] = set(c.get("color_identity", [])) legal[c["name"]] = c.get("legalities", {}).get("commander") == "legal" sims = emb @ emb[idx[card]] print(f"~~ mechanically similar to {card} ({''.join(sorted(identity))}-legal only) ~~") for i in np.argsort(-sims): n = names[i] if n == card or not legal.get(n) or not ci.get(n, {"W"}) <= identity: continue print(f" {sims[i]:.3f} {n}") k -= 1 if k == 0: break if __name__ == "__main__": main()