Skip to content

Commit 4611dcd

Browse files
add reset and refresh buttons
Signed-off-by: Jonathan Irvin <djfoxyslpr@gmail.com>
1 parent 876fb5d commit 4611dcd

1 file changed

Lines changed: 108 additions & 90 deletions

File tree

main.py

Lines changed: 108 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,26 @@
4545
# Keys can be "home" and "stream". Each value is a tuple: (container, tile_buttons).
4646
board_views = {}
4747

48+
board_iteration = 1
49+
50+
def generate_board(seed_val: int):
51+
"""
52+
Generate a new board using the provided seed value.
53+
Also resets the clicked_tiles (ensuring the FREE SPACE is clicked) and sets the global today_seed.
54+
"""
55+
global board, today_seed, clicked_tiles
56+
todays_seed = datetime.date.today().strftime("%Y%m%d")
57+
random.seed(seed_val)
58+
shuffled_phrases = random.sample(phrases, 24)
59+
shuffled_phrases.insert(12, FREE_SPACE_TEXT)
60+
board = [shuffled_phrases[i:i+5] for i in range(0, 25, 5)]
61+
clicked_tiles.clear()
62+
for r, row in enumerate(board):
63+
for c, phrase in enumerate(row):
64+
if phrase.upper() == FREE_SPACE_TEXT:
65+
clicked_tiles.add((r, c))
66+
today_seed = f"{todays_seed}.{seed_val}"
67+
4868
def get_line_style_for_lines(line_count: int, default_text_color: str) -> str:
4969
"""
5070
Return a complete style string with an adjusted line-height based on the number of lines
@@ -92,20 +112,13 @@ def has_too_many_repeats(phrase, threshold=0.5):
92112

93113
phrases = [p for p in unique_phrases if not has_too_many_repeats(p)]
94114

95-
# Use today's date as the seed for deterministic shuffling
96-
today_seed = datetime.date.today().strftime("%Y%m%d")
97-
random.seed(int(today_seed)) # Everyone gets the same shuffle per day
98-
99-
# Shuffle and create the 5x5 board:
100-
shuffled_phrases = random.sample(phrases, 24) # Random but fixed order per day
101-
shuffled_phrases.insert(12, FREE_SPACE_TEXT) # Center slot
102-
board = [shuffled_phrases[i:i+5] for i in range(0, 25, 5)]
103-
104115
# Track clicked tiles and store chip references
105116
clicked_tiles = set()
106117
tile_buttons = {} # {(row, col): chip}
107118
tile_icons = {} # {(row, col): icon reference}
108-
admin_checkboxes = {} # {(row, col): admin checkbox element}
119+
120+
# Initialize the board using the default iteration value.
121+
generate_board(board_iteration)
109122

110123
def split_phrase_into_lines(phrase: str, forced_lines: int = None) -> list:
111124
"""
@@ -225,38 +238,9 @@ def check_winner():
225238
ui.notify("BINGO!", color="green", duration=5)
226239

227240
def sync_board_state():
228-
update_tile_styles(tile_buttons)
229-
sync_admin_checkboxes()
230-
update_admin_visibility()
231-
232-
def sync_admin_checkboxes():
233-
"""
234-
Sync the value in each admin panel checkbox with the global clicked_tiles.
235-
"""
236-
for key, chks in admin_checkboxes.items():
237-
new_value = key in clicked_tiles
238-
if "single" in chks and chks["single"].value != new_value:
239-
chks["single"].value = new_value
240-
chks["single"].update()
241-
242-
def update_admin_visibility():
243-
"""
244-
Bind the visibility of the admin checkboxes:
245-
- left copy is visible only when unchecked (value False)
246-
- right copy is visible only when checked (value True)
247-
"""
248-
for key, chks in admin_checkboxes.items():
249-
val = chks["single"].value # both copies hold the same value
250-
chks["single"].set_visibility(not val) # show left box only when unchecked
251-
chks["single"].update()
252-
253-
def admin_checkbox_change(e, key):
254-
# When a checkbox in the admin page is toggled, update the global clicked_tiles
255-
if e.value:
256-
clicked_tiles.add(key)
257-
else:
258-
clicked_tiles.discard(key)
259-
sync_board_state()
241+
# Update tile styles in every board view (e.g., home and stream)
242+
for view_key, (container, tile_buttons_local) in board_views.items():
243+
update_tile_styles(tile_buttons_local)
260244

261245
def create_board_view(background_color: str, is_global: bool):
262246
"""
@@ -265,25 +249,37 @@ def create_board_view(background_color: str, is_global: bool):
265249
otherwise it uses a local board (stream page).
266250
"""
267251
setup_head(background_color)
268-
# Create the board container.
269-
container = ui.element("div").classes("flex justify-center items-center w-full")
252+
# Create the board container. For the home view, assign an ID to capture it.
253+
if is_global:
254+
container = ui.element("div").classes("home-board-container flex justify-center items-center w-full")
255+
ui.run_javascript("document.querySelector('.home-board-container').id = 'board-container'")
256+
else:
257+
container = ui.element("div").classes("stream-board-container flex justify-center items-center w-full")
258+
ui.run_javascript("document.querySelector('.stream-board-container').id = 'board-container-stream'")
259+
270260
if is_global:
271-
global home_board_container, tile_buttons
261+
global home_board_container, tile_buttons, seed_label
272262
home_board_container = container
273263
tile_buttons = {} # Start with an empty dictionary.
274264
build_board(container, tile_buttons, toggle_tile)
275265
board_views["home"] = (container, tile_buttons)
276266
# Add timers for synchronizing the global board.
277267
ui.timer(0.1, sync_board_state)
278268
ui.timer(1, check_phrases_file_change)
269+
global seed_label
270+
with ui.row().classes("w-full mt-4 items-center justify-center gap-4"):
271+
with ui.button("", icon="refresh", on_click=reset_board).classes("rounded-full w-12 h-12") as reset_btn:
272+
ui.tooltip("Reset Board")
273+
with ui.button("", icon="autorenew", on_click=generate_new_board).classes("rounded-full w-12 h-12") as new_board_btn:
274+
ui.tooltip("New Board")
275+
seed_label = ui.label(f"Seed: {today_seed}").classes("text-sm text-center").style(
276+
f"font-family: '{BOARD_TILE_FONT}', sans-serif; color: {TILE_UNCLICKED_BG_COLOR};"
277+
)
279278
else:
280279
local_tile_buttons = {}
281280
build_board(container, local_tile_buttons, toggle_tile)
282281
board_views["stream"] = (container, local_tile_buttons)
283282
ui.timer(0.1, lambda: update_tile_styles(local_tile_buttons))
284-
# Display the seed beneath the board.
285-
with ui.element("div").classes("w-full mt-4"):
286-
ui.label(f"Seed: {today_seed}").classes("text-md text-center").style(f"color: {TILE_UNCLICKED_BG_COLOR};")
287283

288284
@ui.page("/")
289285
def home_page():
@@ -293,44 +289,6 @@ def home_page():
293289
def stream_page():
294290
create_board_view(STREAM_BG_COLOR, False)
295291

296-
@ui.page("/admin")
297-
def admin_page():
298-
def reset_board():
299-
clicked_tiles.clear()
300-
# Re-add FREE SPACE at the center (position (2,2))
301-
clicked_tiles.add((2, 2))
302-
sync_board_state()
303-
build_admin_panel() # rebuild panel to reflect state changes
304-
305-
with ui.row().classes("zd max-w-xl mx-auto p-4") as container:
306-
ui.label("Admin Panel").classes("text-h4 text-center")
307-
ui.button("Reset Board", on_click=reset_board)
308-
309-
def build_admin_panel():
310-
panel.clear() # clear previous panel content
311-
with panel:
312-
with ui.column():
313-
# Single column design: list each tile with a toggle checkbox.
314-
for r in range(5):
315-
for c in range(5):
316-
key = (r, c)
317-
phrase = board[r][c]
318-
with ui.row().classes("items-center"):
319-
ui.label(f"{phrase} ({r},{c})").classes("w-3/4")
320-
def on_checkbox_change(e, key=key):
321-
if e.value:
322-
clicked_tiles.add(key)
323-
else:
324-
clicked_tiles.discard(key)
325-
sync_board_state()
326-
cb = ui.checkbox("", value=(key in clicked_tiles), on_change=on_checkbox_change)
327-
# Save a single reference to this admin checkbox
328-
admin_checkboxes[key] = {"single": cb}
329-
330-
panel = ui.column() # container for the admin panel
331-
build_admin_panel()
332-
ui.timer(0.1, sync_admin_checkboxes)
333-
334292
def setup_head(background_color: str):
335293
"""
336294
Set up common head elements: fonts, fitty JS, and background color.
@@ -345,6 +303,38 @@ def setup_head(background_color: str):
345303
ui.add_head_html(get_google_font_css(BOARD_TILE_FONT, BOARD_TILE_FONT_WEIGHT, BOARD_TILE_FONT_STYLE, "board_tile"))
346304

347305
ui.add_head_html('<script src="https://cdn.jsdelivr.net/npm/fitty@2.3.6/dist/fitty.min.js"></script>')
306+
# Add html2canvas library and capture function.
307+
ui.add_head_html("""
308+
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
309+
<script>
310+
function captureBoardAndDownload(seed) {
311+
var boardElem = document.getElementById('board-container');
312+
if (!boardElem) {
313+
alert("Board container not found!");
314+
return;
315+
}
316+
// Run fitty to ensure text is resized and centered
317+
fitty('.fit-text', { multiLine: true, minSize: 10, maxSize: 1000 });
318+
fitty('.fit-text-small', { multiLine: true, minSize: 10, maxSize: 72 });
319+
320+
// Wait a short period to ensure that the board is fully rendered and styles have settled.
321+
setTimeout(function() {
322+
html2canvas(boardElem, {
323+
useCORS: true,
324+
scale: 10, // Increase scale for higher resolution
325+
logging: true,
326+
backgroundColor: null
327+
}).then(function(canvas) {
328+
var link = document.createElement('a');
329+
link.download = `bingo_board_${seed}.png`; // Include seed in filename
330+
link.href = canvas.toDataURL('image/png');
331+
link.click();
332+
});
333+
}, 500); // Adjust delay if necessary
334+
}
335+
</script>
336+
""")
337+
348338
ui.add_head_html(f'<style>body {{ background-color: {background_color}; }}</style>')
349339

350340
ui.add_head_html("""<script>
@@ -423,7 +413,7 @@ def update_tile_styles(tile_buttons_dict: dict):
423413
phrase = board[r][c]
424414

425415
if (r, c) in clicked_tiles:
426-
new_card_style = f"background-color: {TILE_CLICKED_BG_COLOR}; color: {TILE_CLICKED_TEXT_COLOR}; border: none;"
416+
new_card_style = f"background-color: {TILE_CLICKED_BG_COLOR}; color: {TILE_CLICKED_TEXT_COLOR}; border: 8px solid {TILE_UNCLICKED_BG_COLOR};"
427417
new_label_color = TILE_CLICKED_TEXT_COLOR
428418
else:
429419
new_card_style = f"background-color: {TILE_UNCLICKED_BG_COLOR}; color: {TILE_UNCLICKED_TEXT_COLOR}; border: none;"
@@ -497,9 +487,7 @@ def has_too_many_repeats(phrase, threshold=0.5):
497487

498488
phrases = [p for p in unique_phrases if not has_too_many_repeats(p)]
499489
# Rebuild board data: re-shuffle and re-create board structure.
500-
shuffled_phrases = random.sample(phrases, 24)
501-
shuffled_phrases.insert(12, FREE_SPACE_TEXT)
502-
board = [shuffled_phrases[i:i+5] for i in range(0, 25, 5)]
490+
generate_board(board_iteration)
503491
# Update all board views (both home and stream)
504492
for view, (container, tile_buttons_local) in board_views.items():
505493
container.clear()
@@ -511,5 +499,35 @@ def has_too_many_repeats(phrase, threshold=0.5):
511499
"fitty('.fit-text-small', { multiLine: true, minSize: 10, maxSize: 72 });"
512500
)
513501

502+
def reset_board():
503+
"""
504+
Reset the board by clearing all clicked states and re-adding the FREE SPACE.
505+
"""
506+
clicked_tiles.clear()
507+
for r, row in enumerate(board):
508+
for c, phrase in enumerate(row):
509+
if phrase.upper() == FREE_SPACE_TEXT:
510+
clicked_tiles.add((r, c))
511+
sync_board_state()
512+
513+
def generate_new_board():
514+
"""
515+
Generate a new board with an incremented iteration seed and update all board views.
516+
"""
517+
global board_iteration
518+
board_iteration += 1
519+
generate_board(board_iteration)
520+
# Update all board views (both home and stream)
521+
for view_key, (container, tile_buttons_local) in board_views.items():
522+
container.clear()
523+
tile_buttons_local.clear()
524+
build_board(container, tile_buttons_local, toggle_tile)
525+
container.update()
526+
# Update the seed label if available
527+
if 'seed_label' in globals():
528+
seed_label.set_text(f"Seed: {today_seed}")
529+
seed_label.update()
530+
reset_board()
531+
514532
# Run the NiceGUI app
515533
ui.run(port=8080, title="Commit Bingo", dark=False)

0 commit comments

Comments
 (0)