BattleWords / battlewords /word_loader.py
Surn's picture
UI update, wordlists logic
50f9808
raw
history blame
3.87 kB
from __future__ import annotations
import re
import os
from typing import Dict, List, Optional
import streamlit as st
from importlib import resources
# Minimal built-ins used if the external file is missing or too small
FALLBACK_WORDS: Dict[int, List[str]] = {
4: [
"TREE", "BOAT", "WIND", "FROG", "LION", "MOON", "FORK", "GLOW", "GAME", "CODE",
"DATA", "BLUE", "GOLD", "ROAD", "STAR",
],
5: [
"APPLE", "RIVER", "STONE", "PLANT", "MOUSE", "BOARD", "CHAIR", "SCALE", "SMILE", "CLOUD",
],
6: [
"ORANGE", "PYTHON", "STREAM", "MARKET", "FOREST", "THRIVE", "LOGGER", "BREATH", "DOMAIN", "GALAXY",
],
}
def get_wordlist_files() -> list[str]:
words_dir = os.path.join(os.path.dirname(__file__), "words")
if not os.path.isdir(words_dir):
return []
files = [f for f in os.listdir(words_dir) if f.lower().endswith(".txt")]
return sorted(files)
@st.cache_data(show_spinner=False)
def load_word_list(selected_file: Optional[str] = None) -> Dict[int, List[str]]:
"""
Load a word list, filter to uppercase A–Z, lengths in {4,5,6}, and dedupe while preserving order.
If `selected_file` is provided, load battlewords/words/<selected_file>.
Otherwise, try packaged resource battlewords/words/wordlist.txt.
If fewer than 500 entries exist for any required length, fall back to built-ins
for that length (per specs).
"""
words_by_len: Dict[int, List[str]] = {4: [], 5: [], 6: []}
used_source = "fallback"
def _finalize(wbl: Dict[int, List[str]], source: str) -> Dict[int, List[str]]:
try:
st.session_state.wordlist_source = source
st.session_state.wordlist_selected = selected_file or "wordlist.txt"
st.session_state.word_counts = {k: len(v) for k, v in wbl.items()}
except Exception:
pass
return wbl
def _read_text_from_disk(fname: str) -> str:
words_dir = os.path.join(os.path.dirname(__file__), "words")
path = os.path.join(words_dir, fname)
with open(path, "r", encoding="utf-8") as f:
return f.read()
try:
text: Optional[str] = None
if selected_file:
# Prefer explicit selection from words/ directory.
text = _read_text_from_disk(selected_file)
else:
# Fallback to packaged default wordlist.txt
text = resources.files("battlewords.words").joinpath("wordlist.txt").read_text(encoding="utf-8")
seen = {4: set(), 5: set(), 6: set()}
for raw in text.splitlines():
line = raw.strip()
if not line or line.startswith("#"):
continue
if "#" in line:
line = line.split("#", 1)[0].strip()
word = line.upper()
if not re.fullmatch(r"[A-Z]+", word):
continue
L = len(word)
if L in (4, 5, 6) and word not in seen[L]:
words_by_len[L].append(word)
seen[L].add(word)
counts = {k: len(v) for k, v in words_by_len.items()}
if all(counts[k] >= 250 for k in (4, 5, 6)):
used_source = "file"
return _finalize(words_by_len, used_source)
# Per spec: fallback for any length below threshold
mixed: Dict[int, List[str]] = {
4: words_by_len[4] if counts[4] >= 250 else FALLBACK_WORDS[4],
5: words_by_len[5] if counts[5] >= 250 else FALLBACK_WORDS[5],
6: words_by_len[6] if counts[6] >= 250 else FALLBACK_WORDS[6],
}
used_source = "file+fallback" if any(counts[k] >= 250 for k in (4, 5, 6)) else "fallback"
return _finalize(mixed, used_source)
except Exception:
# Missing file or read error
used_source = "fallback"
return _finalize(FALLBACK_WORDS, used_source)