- Slab catalog and effect handlers ported from WhiteDog1004/sephiria - Hill-climbing solver maximizes effect sum on slab-occupied cells - PIL renderer outputs PNG with effects overlay; downloads + caches slab images from img.sephiria.wiki on demand - Tkinter GUI for picking slabs by tier; CLI also available - Screenshot recognizer (template matching, beta) - build.bat / build.sh for portable single-file builds via PyInstaller
114 lines
3.7 KiB
Python
114 lines
3.7 KiB
Python
"""Entry point: GUI by default, CLI when --cli is passed."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
from typing import List
|
|
|
|
from .renderer import save_solution
|
|
from .slabs import SLABS, SLABS_BY_VALUE
|
|
from .solver import solve
|
|
|
|
|
|
def _cli(argv: List[str]) -> int:
|
|
p = argparse.ArgumentParser(
|
|
prog="sephiria_inv",
|
|
description="Optimize Sephiria slab placement and render the result.",
|
|
)
|
|
p.add_argument(
|
|
"--cli", action="store_true",
|
|
help="Run in CLI mode (no GUI).",
|
|
)
|
|
p.add_argument(
|
|
"-s", "--slab", action="append", default=[],
|
|
help="Slab value to include. Use value (e.g. 'harvesting') with optional ':N' "
|
|
"multiplier (e.g. 'harvesting:3'). Repeat as needed.",
|
|
)
|
|
p.add_argument(
|
|
"--slots", type=int, default=34, help="Inventory slot count (18..60).",
|
|
)
|
|
p.add_argument("--seed", type=int, default=None)
|
|
p.add_argument(
|
|
"--time-limit", type=float, default=4.0,
|
|
help="Solver time budget in seconds.",
|
|
)
|
|
p.add_argument(
|
|
"-o", "--output", default="sephiria_layout.png",
|
|
help="Output PNG path.",
|
|
)
|
|
p.add_argument(
|
|
"--list", action="store_true",
|
|
help="List known slab values and exit.",
|
|
)
|
|
p.add_argument(
|
|
"--no-download", action="store_true",
|
|
help="Skip CDN download (text-only render).",
|
|
)
|
|
p.add_argument(
|
|
"--screenshot", default=None,
|
|
help="Read slab list from a game screenshot (PNG/JPG).",
|
|
)
|
|
p.add_argument(
|
|
"--bbox", default=None,
|
|
help="Required with --screenshot. Pixel bbox of the inventory grid as "
|
|
"'left,top,right,bottom'.",
|
|
)
|
|
args = p.parse_args(argv)
|
|
|
|
if args.list:
|
|
for s in SLABS:
|
|
print(f" {s.value:<16s} {s.ko_label:<6s} ({s.tier})")
|
|
return 0
|
|
|
|
basket: List[str] = []
|
|
if args.screenshot:
|
|
if not args.bbox:
|
|
print("--screenshot 사용 시 --bbox 'l,t,r,b' 가 필요합니다.", file=sys.stderr)
|
|
return 2
|
|
try:
|
|
l, t, r, b = (int(v) for v in args.bbox.split(","))
|
|
except ValueError:
|
|
print("--bbox 형식: left,top,right,bottom (정수)", file=sys.stderr)
|
|
return 2
|
|
from .screenshot import recognize, recognized_values
|
|
recs = recognize(args.screenshot, (l, t, r, b), slot_num=args.slots)
|
|
basket.extend(recognized_values(recs))
|
|
print(f"인식된 석판: {len(basket)}개 from screenshot")
|
|
for raw in args.slab:
|
|
if ":" in raw:
|
|
v, n = raw.split(":", 1)
|
|
try:
|
|
count = int(n)
|
|
except ValueError:
|
|
print(f"잘못된 개수: {raw}", file=sys.stderr)
|
|
return 2
|
|
else:
|
|
v, count = raw, 1
|
|
if v not in SLABS_BY_VALUE:
|
|
print(f"알 수 없는 석판 value: {v} (use --list)", file=sys.stderr)
|
|
return 2
|
|
basket.extend([v] * count)
|
|
|
|
if not basket:
|
|
print("최소 하나 이상의 --slab 을 지정하세요.", file=sys.stderr)
|
|
return 2
|
|
|
|
sol = solve(basket, slot_num=args.slots, time_limit=args.time_limit, seed=args.seed)
|
|
save_solution(sol, args.output, download=not args.no_download)
|
|
print(f"score={sol.score} placed={len(sol.placements)} → {args.output}")
|
|
return 0
|
|
|
|
|
|
def main(argv: List[str] | None = None) -> int:
|
|
argv = list(argv if argv is not None else sys.argv[1:])
|
|
if "--cli" in argv or "--list" in argv or any(a.startswith("-s") or a.startswith("--slab") for a in argv):
|
|
return _cli(argv)
|
|
# Fall back to GUI
|
|
from .gui import main as gui_main
|
|
return gui_main()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|