Surn commited on
Commit
f50a1b0
Β·
1 Parent(s): 02ecbe1

- fix challenge mode link
- challenge mode UI improvements

README.md CHANGED
@@ -121,6 +121,10 @@ docker run -p8501:8501 battlewords
121
  - Persistent Storage: all game results saved locally for personal statistics without accounts.
122
  - Challenge Mode: remote storage of challenge results, multi-user leaderboard, and shareable links.
123
 
 
 
 
 
124
  -0.2.21
125
  - fix tests
126
 
 
121
  - Persistent Storage: all game results saved locally for personal statistics without accounts.
122
  - Challenge Mode: remote storage of challenge results, multi-user leaderboard, and shareable links.
123
 
124
+ -0.2.22
125
+ - fix challenge mode link
126
+ - challenge mode UI improvements
127
+
128
  -0.2.21
129
  - fix tests
130
 
battlewords/__init__.py CHANGED
@@ -1,2 +1,2 @@
1
- __version__ = "0.2.21"
2
  __all__ = ["models", "generator", "logic", "ui", "game_storage"]
 
1
+ __version__ = "0.2.22"
2
  __all__ = ["models", "generator", "logic", "ui", "game_storage"]
battlewords/game_storage.py CHANGED
@@ -419,12 +419,14 @@ def load_game_from_sid(
419
  return None
420
 
421
 
422
- def get_shareable_url(sid: str) -> str:
423
  """
424
  Generate a shareable URL from a short ID.
 
425
 
426
  Args:
427
  sid: Short ID (8 characters)
 
428
 
429
  Returns:
430
  str: Full shareable URL
@@ -432,9 +434,24 @@ def get_shareable_url(sid: str) -> str:
432
  Example:
433
  >>> url = get_shareable_url("abc12345")
434
  >>> print(url)
435
- https://Surn/BattleWords/?game_id=abc12345
436
  """
437
- return f"https://{SPACE_NAME}/?game_id={sid}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
 
439
 
440
  if __name__ == "__main__":
 
419
  return None
420
 
421
 
422
+ def get_shareable_url(sid: str, base_url: str = None) -> str:
423
  """
424
  Generate a shareable URL from a short ID.
425
+ If running locally, use localhost. Otherwise, use HuggingFace Space domain.
426
 
427
  Args:
428
  sid: Short ID (8 characters)
429
+ base_url: Optional override for the base URL (for testing or custom deployments)
430
 
431
  Returns:
432
  str: Full shareable URL
 
434
  Example:
435
  >>> url = get_shareable_url("abc12345")
436
  >>> print(url)
437
+ https://surn-battlewords.hf.space/?game_id=abc12345
438
  """
439
+ import os
440
+ from battlewords.modules.constants import SPACE_NAME
441
+
442
+ # 1. If base_url is provided, use it directly
443
+ if base_url:
444
+ return f"{base_url.rstrip('/')}/?game_id={sid}"
445
+
446
+ # 2. Check for local development (common Streamlit env vars)
447
+ port = os.environ.get("PORT") or os.environ.get("STREAMLIT_SERVER_PORT") or "8501"
448
+ host = os.environ.get("HOST") or os.environ.get("STREAMLIT_SERVER_ADDRESS") or "localhost"
449
+ if host in ("localhost", "127.0.0.1") or os.environ.get("IS_LOCAL", "").lower() == "true":
450
+ return f"http://{host}:{port}/?game_id={sid}"
451
+
452
+ # 3. Otherwise, build HuggingFace Space URL from SPACE_NAME
453
+ space = (SPACE_NAME or "surn/battlewords").lower().replace("/", "-")
454
+ return f"https://{space}.hf.space/?game_id={sid}"
455
 
456
 
457
  if __name__ == "__main__":
battlewords/ui.py CHANGED
@@ -495,81 +495,80 @@ def _render_header():
495
  st.title(f"Battlewords v{version}")
496
 
497
  st.subheader("Reveal letters in cells, then guess the words!")
498
- # st.markdown(
499
- # "- Grid is 12Γ—12 with 6 words (two 4-letter, two 5-letter, two 6-letter).\n"
500
- # "- After each reveal, you may submit one word guess below.\n"
501
- # "- Scoring: length + unrevealed letters of that word at guess time.\n"
502
- # "- Score Board: radar of last letter of word, score and status.\n"
503
- # "- Words do not overlap, but may be touching.")
504
- with st.expander("🎯 Challenge Mode (click to expand/collapse)", expanded=True):
505
- # Show Challenge Mode banner if loading a shared game
506
- shared_settings = st.session_state.get("shared_game_settings")
507
- if shared_settings:
508
- users = shared_settings.get("users", [])
509
-
510
- if users:
511
- # Sort users by score (descending), then by time (ascending)
512
- sorted_users = sorted(users, key=lambda u: (-u["score"], u["time"]))
513
- best_user = sorted_users[0]
514
- best_score = best_user["score"]
515
- best_time = best_user["time"]
516
- mins, secs = divmod(best_time, 60)
517
- best_time_str = f"{mins:02d}:{secs:02d}"
518
-
519
- # Build leaderboard HTML
520
- leaderboard_rows = []
521
- for i, user in enumerate(sorted_users[:5], 1): # Top 5
522
- u_mins, u_secs = divmod(user["time"], 60)
523
- u_time_str = f"{u_mins:02d}:{u_secs:02d}"
524
- medal = ["πŸ₯‡", "πŸ₯ˆ", "πŸ₯‰"][i-1] if i <= 3 else f"{i}."
525
- leaderboard_rows.append(
526
- f"<div style='padding:0.25rem; font-size:0.85rem;'>{medal} {user['username']}: {user['score']} pts in {u_time_str}</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
527
  )
528
- leaderboard_html = "".join(leaderboard_rows)
529
-
530
- st.markdown(
531
- f"""
532
- <div style="
533
- background: linear-gradient(90deg, #1d64c8 0%, #165ba8 100%);
534
- color: white;
535
- padding: 1rem;
536
- border-radius: 0.5rem;
537
- margin-bottom: 1rem;
538
- text-align: center;
539
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
540
- ">
541
- 🎯 <strong>CHALLENGE MODE</strong> 🎯<br/>
542
- <span style="font-size: 0.9rem;">
543
- Beat the best: <strong>{best_score} points</strong> in <strong>{best_time_str}</strong> by <strong>{best_user['username']}</strong>
544
- </span>
545
- <div style="margin-top:0.75rem; border-top: 1px solid rgba(255,255,255,0.3); padding-top:0.5rem;">
546
- <strong style="font-size:0.9rem;">πŸ† Leaderboard</strong>
547
- {leaderboard_html}
548
  </div>
549
- </div>
550
- """,
551
- unsafe_allow_html=True
552
- )
553
- else:
554
- st.markdown(
555
- """
556
- <div style="
557
- background: linear-gradient(90deg, #1d64c8 0%, #165ba8 100%);
558
- color: white;
559
- padding: 1rem;
560
- border-radius: 0.5rem;
561
- margin-bottom: 1rem;
562
- text-align: center;
563
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
564
- ">
565
- 🎯 <strong>CHALLENGE MODE</strong> 🎯<br/>
566
- <span style="font-size: 0.9rem;">
567
- Be the first to complete this challenge!
568
- </span>
569
- </div>
570
- """,
571
- unsafe_allow_html=True
572
- )
573
 
574
  inject_styles()
575
 
@@ -1642,9 +1641,6 @@ def _render_game_over(state: GameState):
1642
  _mount_background_audio(True, src_url, (st.session_state.get("music_volume", 100)) / 100)
1643
  else:
1644
  _mount_background_audio(False, None, 0.0)
1645
- else:
1646
- # Disable all music playback
1647
- _mount_background_audio(False, None, 0.0)
1648
 
1649
  def run_app():
1650
  # Handle query params using new API
 
495
  st.title(f"Battlewords v{version}")
496
 
497
  st.subheader("Reveal letters in cells, then guess the words!")
498
+
499
+ # Only show Challenge Mode expander if in challenge mode and game_id is present
500
+ params = st.query_params if hasattr(st, "query_params") else {}
501
+ is_challenge_mode = "shared_game_settings" in st.session_state and "game_id" in params
502
+
503
+ if is_challenge_mode:
504
+ with st.expander("🎯 Challenge Mode (click to expand/collapse)", expanded=True):
505
+ shared_settings = st.session_state.get("shared_game_settings")
506
+ if shared_settings:
507
+ users = shared_settings.get("users", [])
508
+
509
+ if users:
510
+ # Sort users by score (descending), then by time (ascending)
511
+ sorted_users = sorted(users, key=lambda u: (-u["score"], u["time"]))
512
+ best_user = sorted_users[0]
513
+ best_score = best_user["score"]
514
+ best_time = best_user["time"]
515
+ mins, secs = divmod(best_time, 60)
516
+ best_time_str = f"{mins:02d}:{secs:02d}"
517
+
518
+ # Build leaderboard HTML
519
+ leaderboard_rows = []
520
+ for i, user in enumerate(sorted_users[:5], 1): # Top 5
521
+ u_mins, u_secs = divmod(user["time"], 60)
522
+ u_time_str = f"{u_mins:02d}:{u_secs:02d}"
523
+ medal = ["πŸ₯‡", "πŸ₯ˆ", "πŸ₯‰"][i-1] if i <= 3 else f"{i}."
524
+ leaderboard_rows.append(
525
+ f"<div style='padding:0.25rem; font-size:0.85rem;'>{medal} {user['username']}: {user['score']} pts in {u_time_str}</div>"
526
+ )
527
+ leaderboard_html = "".join(leaderboard_rows)
528
+
529
+ st.markdown(
530
+ f"""
531
+ <div style="
532
+ background: linear-gradient(90deg, #1d64c8 0%, #165ba8 100%);
533
+ color: white;
534
+ padding: 1rem;
535
+ border-radius: 0.5rem;
536
+ margin-bottom: 1rem;
537
+ text-align: center;
538
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
539
+ ">
540
+ 🎯 <strong>CHALLENGE MODE</strong> 🎯<br/>
541
+ <span style="font-size: 0.9rem;">
542
+ Beat the best: <strong>{best_score} points</strong> in <strong>{best_time_str}</strong> by <strong>{best_user['username']}</strong>
543
+ </span>
544
+ <div style="margin-top:0.75rem; border-top: 1px solid rgba(255,255,255,0.3); padding-top:0.5rem;">
545
+ <strong style="font-size:0.9rem;">πŸ† Leaderboard</strong>
546
+ {leaderboard_html}
547
+ </div>
548
+ </div>
549
+ """,
550
+ unsafe_allow_html=True
551
  )
552
+ else:
553
+ st.markdown(
554
+ """
555
+ <div style="
556
+ background: linear-gradient(90deg, #1d64c8 0%, #165ba8 100%);
557
+ color: white;
558
+ padding: 1rem;
559
+ border-radius: 0.5rem;
560
+ margin-bottom: 1rem;
561
+ text-align: center;
562
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
563
+ ">
564
+ 🎯 <strong>CHALLENGE MODE</strong> 🎯<br/>
565
+ <span style="font-size: 0.9rem;">
566
+ Be the first to complete this challenge!
567
+ </span>
 
 
 
 
568
  </div>
569
+ """,
570
+ unsafe_allow_html=True
571
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572
 
573
  inject_styles()
574
 
 
1641
  _mount_background_audio(True, src_url, (st.session_state.get("music_volume", 100)) / 100)
1642
  else:
1643
  _mount_background_audio(False, None, 0.0)
 
 
 
1644
 
1645
  def run_app():
1646
  # Handle query params using new API