Spaces:
Running
Running
| """ | |
| Saved character library — name a character once, reuse it in any movie. | |
| Custom characters live in characters.json and resolve everywhere draw.draw_sprite | |
| is used; a saved name takes precedence over the built-in library. Add them from | |
| ASCII art you provide, or let the model draw one from a description. | |
| python characters.py list | |
| python characters.py add "fox king" --desc "a fox wearing a tiny crown" --color orange | |
| python characters.py add robo --art-file robo.txt --color gray | |
| python characters.py show "fox king" | |
| python characters.py rm robo | |
| A leaf module: it imports nothing from the package at module load (draw.py imports | |
| THIS), so there is no cycle; add() imports draw lazily only when it must draw. | |
| """ | |
| import argparse | |
| import json | |
| import os | |
| STORE = os.path.join(os.path.dirname(__file__), "characters.json") | |
| def _load() -> dict: | |
| if not os.path.exists(STORE): | |
| return {} | |
| try: | |
| return json.load(open(STORE)) | |
| except (OSError, json.JSONDecodeError): | |
| return {} | |
| def _save(d: dict) -> None: | |
| json.dump(d, open(STORE, "w"), ensure_ascii=False, indent=2) | |
| def library() -> dict: | |
| return _load() | |
| def get(name: str | None) -> list[str] | None: | |
| e = _load().get((name or "").strip().lower()) | |
| return e["art"] if e else None | |
| def color(name: str | None) -> str | None: | |
| e = _load().get((name or "").strip().lower()) | |
| return e.get("color") if e else None | |
| def save(name: str, art: list[str], color: str | None = None, desc: str = "") -> str: | |
| key = name.strip().lower() | |
| d = _load() | |
| d[key] = {"art": art, "color": color, "desc": desc} | |
| _save(d) | |
| return key | |
| def add(name: str, desc: str = "", art: list[str] | None = None, color: str | None = None) -> str: | |
| """Add a character: use provided `art`, else draw it from `desc`/name.""" | |
| import draw # lazy: draw imports characters, so import it only when drawing | |
| art = draw._normalize(art) if art is not None else draw.draw_sprite(name, desc) | |
| return save(name, art, color, desc) | |
| def remove(name: str) -> None: | |
| d = _load() | |
| d.pop(name.strip().lower(), None) | |
| _save(d) | |
| def main(): | |
| ap = argparse.ArgumentParser(description="Manage the saved character library.") | |
| sub = ap.add_subparsers(dest="cmd", required=True) | |
| sub.add_parser("list") | |
| a = sub.add_parser("add") | |
| a.add_argument("name") | |
| a.add_argument("--desc", default="") | |
| a.add_argument("--art-file", help="a text file of ASCII art (<=7 rows, <=14 cols)") | |
| a.add_argument("--color", help="a palette colour name (e.g. orange, cyan, red)") | |
| s = sub.add_parser("show"); s.add_argument("name") | |
| r = sub.add_parser("rm"); r.add_argument("name") | |
| args = ap.parse_args() | |
| if args.cmd == "list": | |
| lib = library() | |
| if not lib: | |
| print("(no saved characters yet)") | |
| for k, e in sorted(lib.items()): | |
| print(f" {k} [{e.get('color') or 'auto'}] {e.get('desc','')}") | |
| elif args.cmd == "add": | |
| art = open(args.art_file, encoding="utf-8").read().splitlines() if args.art_file else None | |
| key = add(args.name, desc=args.desc, art=art, color=args.color) | |
| print(f"saved '{key}':") | |
| print("\n".join(get(key))) | |
| elif args.cmd == "show": | |
| art = get(args.name) | |
| print("\n".join(art) if art else f"(no character named '{args.name}')") | |
| elif args.cmd == "rm": | |
| remove(args.name) | |
| print(f"removed '{args.name}'") | |
| if __name__ == "__main__": | |
| main() | |