Surn commited on
Commit
69700df
·
1 Parent(s): 0d65c71

Radar scope update 1

Browse files
Files changed (2) hide show
  1. battlewords/__init__.py +1 -1
  2. battlewords/ui.py +73 -6
battlewords/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.1.3"
2
  __all__ = ["models", "generator", "logic", "ui"]
 
1
+ __version__ = "0.1.4"
2
  __all__ = ["models", "generator", "logic", "ui"]
battlewords/ui.py CHANGED
@@ -3,8 +3,11 @@ from . import __version__ as version
3
  from typing import Iterable, Tuple, Optional
4
 
5
  import matplotlib.pyplot as plt
 
 
6
  import tempfile
7
  import os
 
8
 
9
  from .generator import generate_puzzle, sort_word_file
10
  from .logic import build_letter_map, reveal_cell, guess_word, is_game_over, compute_tier
@@ -14,6 +17,12 @@ from .word_loader import get_wordlist_files, load_word_list # use loader direct
14
 
15
  CoordLike = Tuple[int, int]
16
 
 
 
 
 
 
 
17
 
18
  def _coord_to_xy(c) -> CoordLike:
19
  # Supports dataclass Coord(x, y) or a 2-tuple/list.
@@ -288,6 +297,45 @@ def _render_sidebar():
288
  else:
289
  st.info("No word lists found in words/ directory. Using built-in fallback.")
290
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
 
292
  def _render_radar(puzzle: Puzzle, size: int, r_max: float = 0.85, max_frames: int = 30, sinusoid_expand: bool = True, stagger_radar: bool = False):
293
  import numpy as np
@@ -318,11 +366,19 @@ def _render_radar(puzzle: Puzzle, size: int, r_max: float = 0.85, max_frames: in
318
  r_min = 0.15
319
  ring_linewidth = 4 # thickness of the ring stroke
320
 
321
- fig, ax = plt.subplots(figsize=(3, 3))
 
 
 
 
 
 
322
  ax.set_xlim(0.2, size)
323
  ax.set_ylim(size, 0.2)
324
  ax.set_xticks(range(1, size + 1))
325
  ax.set_yticks(range(1, size + 1))
 
 
326
  ax.grid(True, which="both", linestyle="--", alpha=0.3)
327
  # ax.set_title("Radar")
328
  ax.set_aspect('equal', adjustable='box')
@@ -366,14 +422,25 @@ def _render_radar(puzzle: Puzzle, size: int, r_max: float = 0.85, max_frames: in
366
  bg_ax.imshow(grad_img, aspect='auto', interpolation='bilinear')
367
  bg_ax.axis('off')
368
 
 
 
 
 
 
369
  # Main axes above the background, with solid interior color for the radar grid
370
- ax.set_facecolor('#4b7bc4') # interior of the radar
371
- ax.set_zorder(1)
 
 
 
 
 
 
372
 
373
  # Create ring (annulus-like) patches for each radar blip using Circle with stroke
374
  rings: list[Circle] = []
375
  for x, y in zip(xs, ys):
376
- ring = Circle((x, y), radius=r_min, fill=False, edgecolor='#9ceffe', linewidth=ring_linewidth, alpha=1.0, zorder=2)
377
  ax.add_patch(ring)
378
  rings.append(ring)
379
 
@@ -405,8 +472,8 @@ def _render_radar(puzzle: Puzzle, size: int, r_max: float = 0.85, max_frames: in
405
  # Read the GIF into memory
406
  tmpfile.seek(0)
407
  gif_bytes = tmpfile.read()
408
- st.image(gif_bytes, width='stretch')
409
- os.unlink(tmpfile.name) # Now safe to delete
410
 
411
 
412
  def _render_grid(state: GameState, letter_map):
 
3
  from typing import Iterable, Tuple, Optional
4
 
5
  import matplotlib.pyplot as plt
6
+ from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
7
+ from matplotlib import colors as mcolors
8
  import tempfile
9
  import os
10
+ from PIL import Image
11
 
12
  from .generator import generate_puzzle, sort_word_file
13
  from .logic import build_letter_map, reveal_cell, guess_word, is_game_over, compute_tier
 
17
 
18
  CoordLike = Tuple[int, int]
19
 
20
+ def fig_to_pil_rgba(fig):
21
+ canvas = FigureCanvas(fig)
22
+ canvas.draw()
23
+ w, h = fig.canvas.get_width_height()
24
+ img = np.frombuffer(canvas.buffer_rgba(), dtype=np.uint8).reshape(h, w, 4)
25
+ return Image.fromarray(img, mode="RGBA")
26
 
27
  def _coord_to_xy(c) -> CoordLike:
28
  # Supports dataclass Coord(x, y) or a 2-tuple/list.
 
297
  else:
298
  st.info("No word lists found in words/ directory. Using built-in fallback.")
299
 
300
+ def _create_radar_scope(size=4, bgcolor="none", scope_color="green"):
301
+ fig, ax = plt.subplots(figsize=(size, size), dpi=100)
302
+ ax.set_facecolor(bgcolor)
303
+ fig.patch.set_alpha(0.5)
304
+ ax.set_zorder(0)
305
+
306
+
307
+ # Hide decorations but keep patch/frame on
308
+ for spine in ax.spines.values():
309
+ spine.set_visible(False)
310
+ ax.set_xticks([])
311
+ ax.set_yticks([])
312
+
313
+ # Center lines
314
+ ax.axhline(0, color=scope_color, alpha=0.8, zorder=1)
315
+ ax.axvline(0, color=scope_color, alpha=0.8, zorder=1)
316
+ # ax.set_xticks(range(1, size + 1))
317
+ # ax.set_yticks(range(1, size + 1))
318
+
319
+ # Circles at 25% and 50% radius
320
+ for radius in [0.33, 0.66, 1.0]:
321
+ circle = plt.Circle((0, 0), radius, fill=False, color=scope_color, alpha=0.8, zorder=1)
322
+ ax.add_patch(circle)
323
+
324
+ # Radial lines at 0, 30, 45, 90 degrees
325
+ angles = [0, 30, 60, 120, 150, 210, 240, 300, 330]
326
+ for angle in angles:
327
+ rad = np.deg2rad(angle)
328
+ x = np.cos(rad)
329
+ y = np.sin(rad)
330
+ ax.plot([0, x], [0, y], color=scope_color, alpha=0.5, zorder=1)
331
+
332
+ # Set limits and remove axes
333
+ #ax.set_xlim(-0.5, 0.5)
334
+ #ax.set_ylim(-0.5, 0.5)
335
+ ax.set_aspect('equal', adjustable='box')
336
+ #ax.axis('off')
337
+
338
+ return fig, ax
339
 
340
  def _render_radar(puzzle: Puzzle, size: int, r_max: float = 0.85, max_frames: int = 30, sinusoid_expand: bool = True, stagger_radar: bool = False):
341
  import numpy as np
 
366
  r_min = 0.15
367
  ring_linewidth = 4 # thickness of the ring stroke
368
 
369
+ rgba_labels = mcolors.to_rgba("#FFFFFF", 0.7)
370
+ rgba_ticks = mcolors.to_rgba("#FFFFFF", 0.66)
371
+
372
+ fig, ax = _create_radar_scope(size=3, bgcolor="#4b7bc4", scope_color="#ffffff")
373
+ imgscope = fig_to_pil_rgba(fig)
374
+ plt.close(fig)
375
+ fig, ax = plt.subplots(figsize=(3, 3))
376
  ax.set_xlim(0.2, size)
377
  ax.set_ylim(size, 0.2)
378
  ax.set_xticks(range(1, size + 1))
379
  ax.set_yticks(range(1, size + 1))
380
+ ax.tick_params(axis="both", which="both", labelcolor=rgba_labels)
381
+ ax.tick_params(axis="both", which="both", colors=rgba_ticks)
382
  ax.grid(True, which="both", linestyle="--", alpha=0.3)
383
  # ax.set_title("Radar")
384
  ax.set_aspect('equal', adjustable='box')
 
422
  bg_ax.imshow(grad_img, aspect='auto', interpolation='bilinear')
423
  bg_ax.axis('off')
424
 
425
+ # Overlay the radar scope image centered in the main axes
426
+ scope_ax = fig.add_axes([-0.075, -0.075, 1.15, 1.15], zorder=1)
427
+ scope_ax.imshow(imgscope, aspect='auto', interpolation='lanczos')
428
+ scope_ax.axis('off')
429
+
430
  # Main axes above the background, with solid interior color for the radar grid
431
+ ax.set_facecolor('none') # interior of the radar
432
+ ax.set_zorder(2)
433
+ #ax.axis('off')
434
+ # Hide decorations but keep patch/frame on
435
+ for spine in ax.spines.values():
436
+ spine.set_visible(False)
437
+ # ax.set_xticks([])
438
+ # ax.set_yticks([])
439
 
440
  # Create ring (annulus-like) patches for each radar blip using Circle with stroke
441
  rings: list[Circle] = []
442
  for x, y in zip(xs, ys):
443
+ ring = Circle((x, y), radius=r_min, fill=False, edgecolor='#9ceffe', linewidth=ring_linewidth, alpha=1.0, zorder=3)
444
  ax.add_patch(ring)
445
  rings.append(ring)
446
 
 
472
  # Read the GIF into memory
473
  tmpfile.seek(0)
474
  gif_bytes = tmpfile.read()
475
+ st.image(gif_bytes, width='content', output_format="auto")
476
+ os.unlink(tmpfile.name)
477
 
478
 
479
  def _render_grid(state: GameState, letter_map):