Surn commited on
Commit
78346c3
·
1 Parent(s): dce247e

Word Generator Minor Update

Browse files
Files changed (2) hide show
  1. battlewords/generator.py +22 -41
  2. requirements.txt +5 -1
battlewords/generator.py CHANGED
@@ -1,47 +1,12 @@
1
  from __future__ import annotations
2
 
3
  import random
4
- from pathlib import Path
5
- from typing import Dict, List, Optional, Tuple
6
-
7
- import streamlit as st
8
 
 
9
  from .models import Coord, Word, Puzzle
10
 
11
 
12
- # Fallback minimal word pools if file missing or too small
13
- _FALLBACK: Dict[int, List[str]] = {
14
- 4: ["TREE", "BOAT", "WIND", "FROG", "LION", "MOON", "FORK", "GLOW", "GAME", "CODE"],
15
- 5: ["APPLE", "RIVER", "STONE", "PLANT", "MOUSE", "BOARD", "CHAIR", "SCALE", "SMILE", "CLOUD"],
16
- 6: ["ORANGE", "PYTHON", "STREAM", "MARKET", "FOREST", "THRIVE", "LOGGER", "BREATH", "DOMAIN", "GALAXY"],
17
- }
18
-
19
-
20
- @st.cache_data(show_spinner=False)
21
- def load_word_list() -> Dict[int, List[str]]:
22
- """
23
- Loads and filters a word list for lengths 4, 5, 6.
24
- Returns a dict length->words (uppercase, A–Z only).
25
- """
26
- base = Path(__file__).parent / "words" / "wordlist.txt"
27
- words_by_len: Dict[int, List[str]] = {4: [], 5: [], 6: []}
28
- if base.exists():
29
- try:
30
- for line in base.read_text(encoding="utf-8").splitlines():
31
- w = line.strip().upper()
32
- if w.isalpha() and len(w) in (4, 5, 6):
33
- words_by_len[len(w)].append(w)
34
- except Exception:
35
- pass
36
-
37
- # Ensure minimum pools; otherwise fallback
38
- for L in (4, 5, 6):
39
- if len(words_by_len[L]) < 500:
40
- words_by_len[L] = _FALLBACK[L].copy()
41
-
42
- return words_by_len
43
-
44
-
45
  def _fits_and_free(cells: List[Coord], used: set[Coord], size: int) -> bool:
46
  for c in cells:
47
  if not c.in_bounds(size) or c in used:
@@ -65,18 +30,24 @@ def generate_puzzle(
65
  """
66
  Place exactly six words: 2x4, 2x5, 2x6, horizontal or vertical,
67
  no cell overlaps. Radar pulses are last-letter cells.
 
68
  """
69
  rng = random.Random(seed)
70
  words_by_len = words_by_len or load_word_list()
71
  target_lengths = [4, 4, 5, 5, 6, 6]
72
 
73
  used: set[Coord] = set()
 
74
  placed: List[Word] = []
75
 
76
- # Pre-shuffle the word pools for variety but deterministic with seed
77
- pools: Dict[int, List[str]] = {L: words_by_len[L][:] for L in (4, 5, 6)}
78
- for L in pools:
79
- rng.shuffle(pools[L])
 
 
 
 
80
 
81
  attempts = 0
82
  for L in target_lengths:
@@ -94,6 +65,10 @@ def generate_puzzle(
94
  break
95
  attempts += 1
96
 
 
 
 
 
97
  # Try a variety of starts/orientations for this word
98
  for _ in range(50):
99
  direction = rng.choice(["H", "V"])
@@ -109,6 +84,12 @@ def generate_puzzle(
109
  w = Word(cand_text, Coord(row, col), direction)
110
  placed.append(w)
111
  used.update(cells)
 
 
 
 
 
 
112
  placed_ok = True
113
  break
114
 
 
1
  from __future__ import annotations
2
 
3
  import random
4
+ from typing import Dict, List, Optional
 
 
 
5
 
6
+ from .word_loader import load_word_list
7
  from .models import Coord, Word, Puzzle
8
 
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  def _fits_and_free(cells: List[Coord], used: set[Coord], size: int) -> bool:
11
  for c in cells:
12
  if not c.in_bounds(size) or c in used:
 
30
  """
31
  Place exactly six words: 2x4, 2x5, 2x6, horizontal or vertical,
32
  no cell overlaps. Radar pulses are last-letter cells.
33
+ Ensures the same word text is not selected more than once.
34
  """
35
  rng = random.Random(seed)
36
  words_by_len = words_by_len or load_word_list()
37
  target_lengths = [4, 4, 5, 5, 6, 6]
38
 
39
  used: set[Coord] = set()
40
+ used_texts: set[str] = set()
41
  placed: List[Word] = []
42
 
43
+ # Pre-shuffle the word pools for variety but deterministic with seed.
44
+ # Also de-duplicate within each length pool while preserving order.
45
+ pools: Dict[int, List[str]] = {}
46
+ for L in (4, 5, 6):
47
+ # Preserve order and dedupe
48
+ unique_words = list(dict.fromkeys(words_by_len.get(L, [])))
49
+ rng.shuffle(unique_words)
50
+ pools[L] = unique_words
51
 
52
  attempts = 0
53
  for L in target_lengths:
 
65
  break
66
  attempts += 1
67
 
68
+ # Skip words already used to avoid duplicates across placements
69
+ if cand_text in used_texts:
70
+ continue
71
+
72
  # Try a variety of starts/orientations for this word
73
  for _ in range(50):
74
  direction = rng.choice(["H", "V"])
 
84
  w = Word(cand_text, Coord(row, col), direction)
85
  placed.append(w)
86
  used.update(cells)
87
+ used_texts.add(cand_text)
88
+ # Remove from pool so it can't be picked again later
89
+ try:
90
+ pool.remove(cand_text)
91
+ except ValueError:
92
+ pass
93
  placed_ok = True
94
  break
95
 
requirements.txt CHANGED
@@ -1,4 +1,8 @@
1
  altair
2
  pandas
3
  streamlit
4
- # Entry point is now app.py, not main.py
 
 
 
 
 
1
  altair
2
  pandas
3
  streamlit
4
+ matplotlib
5
+ numpy
6
+ pytest
7
+ flake8
8
+ mypy