Skip to content

Commit bebfc98

Browse files
update layout
Signed-off-by: Jonathan Irvin <djfoxyslpr@gmail.com>
1 parent 68b09d1 commit bebfc98

2 files changed

Lines changed: 140 additions & 21 deletions

File tree

main.py

Lines changed: 137 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,14 @@
33
import datetime
44
import logging
55
import asyncio
6+
import os
67

78
# Set up logging
89
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
910

11+
# Global variable to track phrases.txt modification time.
12+
last_phrases_mtime = os.path.getmtime("phrases.txt")
13+
1014
# --- New: Color constants and font ---
1115
TILE_CLICKED_BG_COLOR = "#3b82f6" # Blue background for clicked tiles
1216
TILE_CLICKED_TEXT_COLOR = "white"
@@ -35,9 +39,9 @@ def get_line_style_for_lines(line_count: int, default_text_color: str) -> str:
3539
elif line_count == 2:
3640
lh = "1.2em" # Slightly reduced spacing for two lines.
3741
elif line_count == 3:
38-
lh = "0.9em" # Even tighter spacing for three lines.
42+
lh = "0.75em" # Even tighter spacing for three lines.
3943
else:
40-
lh = "0.9em" # For four or more lines.
44+
lh = "0.7em" # For four or more lines.
4145
return f"font-family: {FONT_FAMILY}; padding: 0; margin: 0; color: {default_text_color}; line-height: {lh};"
4246

4347
# Read phrases from a text file and convert them to uppercase.
@@ -59,22 +63,97 @@ def get_line_style_for_lines(line_count: int, default_text_color: str) -> str:
5963
tile_icons = {} # {(row, col): icon reference}
6064
admin_checkboxes = {} # {(row, col): admin checkbox element}
6165

62-
def split_phrase_into_lines(phrase: str) -> list:
66+
def split_phrase_into_lines(phrase: str, forced_lines: int = None) -> list:
6367
"""
6468
Splits the phrase into balanced lines.
65-
If the phrase has two or fewer words, return it as a single line.
66-
Otherwise, split into two lines at the midpoint.
69+
For phrases of up to 3 words, return one word per line.
70+
For longer phrases, try splitting the phrase into 2, 3, or 4 lines so that the total
71+
number of characters (including spaces) in each line is as similar as possible.
72+
The function will never return more than 4 lines.
73+
If 'forced_lines' is provided (2, 3, or 4), then the candidate with that many lines is chosen
74+
if available; otherwise, the best candidate overall is returned.
6775
"""
6876
words = phrase.split()
69-
if len(words) == 1:
70-
return [words[0]]
71-
elif len(words) == 2:
72-
return [words[0], words[1]]
73-
elif len(words) == 3:
74-
return [words[0], words[1], words[2]]
77+
n = len(words)
78+
if n <= 3:
79+
return words
80+
81+
# Helper: total length of a list of words (including spaces between words).
82+
def segment_length(segment):
83+
return sum(len(word) for word in segment) + (len(segment) - 1 if segment else 0)
84+
85+
candidates = [] # list of tuples: (number_of_lines, diff, candidate)
86+
87+
# 2-line candidate
88+
best_diff_2 = float('inf')
89+
best_seg_2 = None
90+
for i in range(1, n):
91+
seg1 = words[:i]
92+
seg2 = words[i:]
93+
len1 = segment_length(seg1)
94+
len2 = segment_length(seg2)
95+
diff = abs(len1 - len2)
96+
if diff < best_diff_2:
97+
best_diff_2 = diff
98+
best_seg_2 = [" ".join(seg1), " ".join(seg2)]
99+
if best_seg_2 is not None:
100+
candidates.append((2, best_diff_2, best_seg_2))
101+
102+
# 3-line candidate (if at least 4 words)
103+
if n >= 4:
104+
best_diff_3 = float('inf')
105+
best_seg_3 = None
106+
for i in range(1, n-1):
107+
for j in range(i+1, n):
108+
seg1 = words[:i]
109+
seg2 = words[i:j]
110+
seg3 = words[j:]
111+
len1 = segment_length(seg1)
112+
len2 = segment_length(seg2)
113+
len3 = segment_length(seg3)
114+
current_diff = max(len1, len2, len3) - min(len1, len2, len3)
115+
if current_diff < best_diff_3:
116+
best_diff_3 = current_diff
117+
best_seg_3 = [" ".join(seg1), " ".join(seg2), " ".join(seg3)]
118+
if best_seg_3 is not None:
119+
candidates.append((3, best_diff_3, best_seg_3))
120+
121+
# 4-line candidate (if at least 5 words)
122+
if n >= 5:
123+
best_diff_4 = float('inf')
124+
best_seg_4 = None
125+
for i in range(1, n-2):
126+
for j in range(i+1, n-1):
127+
for k in range(j+1, n):
128+
seg1 = words[:i]
129+
seg2 = words[i:j]
130+
seg3 = words[j:k]
131+
seg4 = words[k:]
132+
len1 = segment_length(seg1)
133+
len2 = segment_length(seg2)
134+
len3 = segment_length(seg3)
135+
len4 = segment_length(seg4)
136+
diff = max(len1, len2, len3, len4) - min(len1, len2, len3, len4)
137+
if diff < best_diff_4:
138+
best_diff_4 = diff
139+
best_seg_4 = [" ".join(seg1), " ".join(seg2), " ".join(seg3), " ".join(seg4)]
140+
if best_seg_4 is not None:
141+
candidates.append((4, best_diff_4, best_seg_4))
142+
143+
# If a forced number of lines is specified, try to return that candidate first.
144+
if forced_lines is not None:
145+
forced_candidates = [cand for cand in candidates if cand[0] == forced_lines]
146+
if forced_candidates:
147+
_, _, best_candidate = min(forced_candidates, key=lambda x: x[1])
148+
return best_candidate
149+
150+
# Otherwise, choose the candidate with the smallest diff.
151+
if candidates:
152+
_, best_diff, best_candidate = min(candidates, key=lambda x: x[1])
153+
return best_candidate
75154
else:
76-
mid = round(len(words) / 2)
77-
return [" ".join(words[:mid]), " ".join(words[mid:])]
155+
# fallback (should never happen)
156+
return [" ".join(words)]
78157

79158
# Function to create the Bingo board UI
80159
def create_bingo_board():
@@ -96,7 +175,10 @@ def create_bingo_board():
96175
line_count = len(lines)
97176
for line in lines:
98177
with ui.row().classes("w-full"):
99-
ui.label(line).classes("fit-text text-center select-none").style(get_line_style_for_lines(line_count, default_text_color))
178+
if len(line) <= 3:
179+
ui.label(line).classes("fit-text-small text-center select-none").style(get_line_style_for_lines(line_count, default_text_color))
180+
else:
181+
ui.label(line).classes("fit-text text-center select-none").style(get_line_style_for_lines(line_count, default_text_color))
100182

101183
tile_buttons[(row_idx, col_idx)] = card
102184

@@ -169,12 +251,17 @@ def home_page():
169251
# Set up NiceGUI page and head elements
170252
setup_head(HOME_BG_COLOR)
171253

172-
global tile_buttons
173-
tile_buttons = build_board(ui.element("div").classes("flex justify-center items-center w-full"), tile_buttons, toggle_tile)
254+
global home_board_container, tile_buttons
255+
home_board_container = ui.element("div").classes("flex justify-center items-center w-full")
256+
tile_buttons = {} # Start with an empty dictionary.
257+
build_board(home_board_container, tile_buttons, toggle_tile)
174258

175-
# Add a timer that calls sync_board_state every 1 second to push state updates to all clients
259+
# Add a timer that calls sync_board_state every 0.1 second to push state updates to all clients
176260
ui.timer(0.1, sync_board_state)
177261

262+
# Add a timer to check if phrases.txt has changed
263+
ui.timer(1, check_phrases_file_change)
264+
178265
with ui.element("div").classes("w-full mt-4"):
179266
ui.label(f"Seed: {today_seed}").classes("text-md text-gray-300 text-center")
180267

@@ -242,10 +329,12 @@ def setup_head(background_color: str):
242329
ui.add_head_html("""<script>
243330
document.addEventListener('DOMContentLoaded', () => {
244331
fitty('.fit-text', { multiLine: true, minSize: 10, maxSize: 1000 });
332+
fitty('.fit-text-small', { multiLine: true, minSize: 10, maxSize: 72 });
245333
fitty('.fit-header', { multiLine: true, minSize: 10, maxSize: 2000 });
246334
});
247335
window.addEventListener('resize', () => {
248336
fitty('.fit-text', { multiLine: true, minSize: 10, maxSize: 1000 });
337+
fitty('.fit-text-small', { multiLine: true, minSize: 10, maxSize: 72 });
249338
fitty('.fit-header', { multiLine: true, minSize: 10, maxSize: 2000 });
250339
});
251340
</script>""")
@@ -275,7 +364,10 @@ def build_board(parent, tile_buttons_dict: dict, on_tile_click):
275364
line_count = len(lines)
276365
for line in lines:
277366
with ui.row().classes("w-full"):
278-
ui.label(line).classes("fit-text text-center select-none").style(get_line_style_for_lines(line_count, default_text_color))
367+
if len(line) <= 3:
368+
ui.label(line).classes("fit-text-small text-center select-none").style(get_line_style_for_lines(line_count, default_text_color))
369+
else:
370+
ui.label(line).classes("fit-text text-center select-none").style(get_line_style_for_lines(line_count, default_text_color))
279371
tile_buttons_dict[(row_idx, col_idx)] = card
280372
if phrase.upper() == "FREE MEAT":
281373
clicked_tiles.add((row_idx, col_idx))
@@ -298,5 +390,32 @@ def update_tile_styles(tile_buttons_dict: dict):
298390
card.style(new_style)
299391
card.update()
300392

393+
def check_phrases_file_change():
394+
"""
395+
Check if phrases.txt has changed. If so, re-read the file, update the board,
396+
and re-render the board UI.
397+
"""
398+
global last_phrases_mtime, phrases, board, tile_buttons, home_board_container
399+
try:
400+
mtime = os.path.getmtime("phrases.txt")
401+
except Exception as e:
402+
logging.error(f"Error checking phrases.txt: {e}")
403+
return
404+
if mtime != last_phrases_mtime:
405+
logging.info("phrases.txt changed, reloading board.")
406+
last_phrases_mtime = mtime
407+
# Re-read phrases.txt
408+
with open("phrases.txt", "r") as f:
409+
phrases = [line.strip().upper() for line in f if line.strip()]
410+
# Rebuild board data: re-shuffle and re-create board structure.
411+
shuffled_phrases = random.sample(phrases, 24)
412+
shuffled_phrases.insert(12, "FREE MEAT")
413+
board = [shuffled_phrases[i:i+5] for i in range(0, 25, 5)]
414+
# Clear the board UI and rebuild it.
415+
home_board_container.clear()
416+
tile_buttons.clear() # Clear global dictionary.
417+
build_board(home_board_container, tile_buttons, toggle_tile)
418+
home_board_container.update() # Force update so new styles are applied immediately.
419+
301420
# Run the NiceGUI app
302421
ui.run(port=8080, title="Commit Bingo", dark=False)

phrases.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
Can't have nice things
1+
can't have nice things
22
Techno babble
33
Hell yeah!
44
We get a raid
5-
Noice
5+
that's so Noice
66
Position one
77
Hows my audio
88
Someone redeems hydrate
@@ -21,4 +21,4 @@ Sight lines
2121
Talks about palia
2222
Says discord
2323
Dance party
24-
Thats nuts!
24+
That's nuts!

0 commit comments

Comments
 (0)