您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Suggests words containing the required syllable for JKLM.fun Bomb Party game with customizable dictionaries and sorting options
// ==UserScript== // @name Bomb Party Suggester // @namespace http://tampermonkey.net/ // @version 0.1.2 // @description Suggests words containing the required syllable for JKLM.fun Bomb Party game with customizable dictionaries and sorting options // @author Doomsy1 // @match *.jklm.fun/games/bombparty* // @grant none // @run-at document-start // @supportURL https://github.com/Doomsy1/Bomb-Party-Suggester/issues // @icon https://www.google.com/s2/favicons?sz=64&domain=jklm.fun // @license MIT // ==/UserScript== (() => { // src/core/typer.js window.BPS = window.BPS || {}; (function() { let KEYBOARD_LAYOUT = { layout: { q: [0, 0], w: [0, 1], e: [0, 2], r: [0, 3], t: [0, 4], y: [0, 5], u: [0, 6], i: [0, 7], o: [0, 8], p: [0, 9], a: [1, 0], s: [1, 1], d: [1, 2], f: [1, 3], g: [1, 4], h: [1, 5], j: [1, 6], k: [1, 7], l: [1, 8], z: [2, 0], x: [2, 1], c: [2, 2], v: [2, 3], b: [2, 4], n: [2, 5], m: [2, 6] }, adjacent: {} }; Object.entries(KEYBOARD_LAYOUT.layout).forEach(([key, [row, col]]) => { KEYBOARD_LAYOUT.adjacent[key] = Object.entries(KEYBOARD_LAYOUT.layout).filter(([k, [r, c]]) => { if (k === key) return !1; let rowDiff = Math.abs(r - row), colDiff = Math.abs(c - col); return rowDiff <= 1 && colDiff <= 1; }).map(([k]) => k); }); let TYPER_CONFIG = { baseDelay: 60, distanceMultiplier: 12.5, minDelay: 15, delayVariation: 0.2, typoChance: 2, typoNoticeDelay: { mean: 250, stdDev: 60 }, typoBackspaceDelay: { mean: 100, stdDev: 40 }, typoRecoveryDelay: { mean: 200, stdDev: 50 } }; function loadSavedSettings() { let saved = localStorage.getItem("bombPartyTyperSettings"); if (saved) try { let parsed = JSON.parse(saved); Object.assign(TYPER_CONFIG, parsed); } catch { } } function saveSettings() { try { localStorage.setItem("bombPartyTyperSettings", JSON.stringify(TYPER_CONFIG)); } catch { } } function normalRandom(mean, stdDev) { let u = 0, v = 0; for (; u === 0; ) u = Math.random(); for (; v === 0; ) v = Math.random(); let num = Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v); return Math.floor(num * stdDev + mean); } function calculateTypingDelay(fromKey, toKey) { if (!fromKey) return TYPER_CONFIG.baseDelay; fromKey = fromKey.toLowerCase(), toKey = toKey.toLowerCase(); let fromPos = KEYBOARD_LAYOUT.layout[fromKey], toPos = KEYBOARD_LAYOUT.layout[toKey]; if (!fromPos || !toPos) return TYPER_CONFIG.baseDelay; let distance = Math.sqrt( Math.pow(fromPos[0] - toPos[0], 2) + Math.pow(fromPos[1] - toPos[1], 2) ), meanDelay = TYPER_CONFIG.baseDelay + distance * TYPER_CONFIG.distanceMultiplier, stdDev = meanDelay * TYPER_CONFIG.delayVariation; return Math.max(TYPER_CONFIG.minDelay, normalRandom(meanDelay, stdDev)); } async function simulateTypo(inputField, correctChar) { let c = correctChar.toLowerCase(); if (!KEYBOARD_LAYOUT.adjacent[c] || Math.random() > TYPER_CONFIG.typoChance / 100) return !1; let neighbors = KEYBOARD_LAYOUT.adjacent[c], typoChar = neighbors[Math.floor(Math.random() * neighbors.length)]; return inputField.value += typoChar, inputField.dispatchEvent(new Event("input", { bubbles: !0 })), await new Promise((resolve) => setTimeout(resolve, calculateTypingDelay(null, typoChar))), await new Promise((resolve) => setTimeout( resolve, normalRandom(TYPER_CONFIG.typoNoticeDelay.mean, TYPER_CONFIG.typoNoticeDelay.stdDev) )), inputField.value = inputField.value.slice(0, -1), inputField.dispatchEvent(new Event("input", { bubbles: !0 })), await new Promise((resolve) => setTimeout( resolve, normalRandom(TYPER_CONFIG.typoBackspaceDelay.mean, TYPER_CONFIG.typoBackspaceDelay.stdDev) )), inputField.value += correctChar, inputField.dispatchEvent(new Event("input", { bubbles: !0 })), await new Promise((resolve) => setTimeout( resolve, normalRandom(TYPER_CONFIG.typoRecoveryDelay.mean, TYPER_CONFIG.typoRecoveryDelay.stdDev) )), !0; } async function simulateTyping(word) { let selfTurn = document.querySelector(".selfTurn"), form = document.querySelector(".selfTurn form"), inputField = document.querySelector(".selfTurn input"); if (!inputField || !form || selfTurn.hidden) return; inputField.value = "", inputField.focus(); let lastChar = null; for (let i = 0; i < word.length; i++) await simulateTypo(inputField, word[i]) || (inputField.value += word[i], inputField.dispatchEvent(new Event("input", { bubbles: !0 })), await new Promise((resolve) => setTimeout(resolve, calculateTypingDelay(lastChar, word[i]))), lastChar = word[i]); form.dispatchEvent(new Event("submit", { bubbles: !0, cancelable: !0 })); } function isPlayerTurn() { let selfTurn = document.querySelector(".selfTurn"); return selfTurn && !selfTurn.hidden; } loadSavedSettings(), window.BPS.KEYBOARD_LAYOUT = KEYBOARD_LAYOUT, window.BPS.TYPER_CONFIG = TYPER_CONFIG, window.BPS.loadSavedSettings = loadSavedSettings, window.BPS.saveSettings = saveSettings, window.BPS.normalRandom = normalRandom, window.BPS.calculateTypingDelay = calculateTypingDelay, window.BPS.simulateTyping = simulateTyping, window.BPS.isPlayerTurn = isPlayerTurn; })(); // src/core/dictionaryLoader.js window.BPS = window.BPS || {}; (function() { let dictionaries = { "5k": { url: "https://raw.githubusercontent.com/filiph/english_words/master/data/word-freq-top5000.csv", words: [], hasFrequency: !0 }, "20k": { url: "https://raw.githubusercontent.com/first20hours/google-10000-english/master/google-10000-english-usa.txt", words: [], hasFrequency: !0 }, "273k": { url: "https://raw.githubusercontent.com/kli512/bombparty-assist/refs/heads/main/bombparty/dictionaries/en.txt", words: [], hasFrequency: !1 } }; async function loadDictionary(size) { let dictionary = dictionaries[size], lines = (await (await fetch(dictionary.url)).text()).split(` `); switch (size) { case "5k": let dataLines = lines.slice(1); dictionary.words = dataLines.map((line) => { let trimmed = line.trim(); if (!trimmed) return { word: "", freq: 0 }; let parts = trimmed.split(","); if (parts.length < 4) return { word: "", freq: 0 }; let word = parts[1] || "", freq = parseInt(parts[3], 10) || 0; return { word, freq }; }); break; case "20k": dictionary.words = lines.map((line, idx) => ({ word: line.trim(), freq: lines.length - idx // higher index = less frequent })); break; case "273k": dictionary.words = lines.filter((line) => line.trim().length > 0).map((line) => ({ word: line.trim().toLowerCase(), freq: 1 // treat all words equally })); break; default: return; } dictionary.words = dictionary.words.filter((entry) => entry.word); } async function loadAllDictionaries() { try { await Promise.all([ loadDictionary("5k"), loadDictionary("20k"), loadDictionary("273k") ]); } catch { } } window.BPS.dictionaries = dictionaries, window.BPS.loadAllDictionaries = loadAllDictionaries; })(); // src/ui/styles.js window.BPS = window.BPS || {}; (function() { let styles = { colors: { primary: "#61dafb", background: "#282c34", text: "#ffffff", highlight: "#2EFF2E", special: "#FF8C00" }, panel: { position: "fixed", top: "10px", right: "10px", backgroundColor: "rgba(40, 44, 52, 0.5)", border: "2px solid #61dafb", borderRadius: "8px", padding: "10px", zIndex: "2147483647", maxWidth: "500px", minWidth: "200px", minHeight: "150px", maxHeight: "800px", width: "300px", height: "400px", fontFamily: "sans-serif", fontSize: "14px", color: "#fff", boxShadow: "0px 4px 12px rgba(0,0,0,0.5)", cursor: "move", resize: "none", overflow: "hidden" }, resizeHandle: { position: "absolute", width: "20px", height: "20px", background: "transparent", zIndex: "2147483647", cursor: "nw-resize" }, resizeDot: { position: "absolute", width: "8px", height: "8px", background: "#61dafb", borderRadius: "50%", left: "50%", top: "50%", transform: "translate(-50%, -50%)" }, resizeEdge: { position: "absolute", background: "transparent", zIndex: "2147483647" }, sizeSelector: { marginBottom: "4px", display: "flex", gap: "8px", justifyContent: "center" }, sortControls: { marginBottom: "8px", display: "flex", gap: "8px", justifyContent: "center", flexWrap: "wrap" }, sortButton: { padding: "4px 8px", border: "1px solid #61dafb", borderRadius: "4px", background: "transparent", color: "#fff", cursor: "pointer", fontSize: "12px", display: "flex", alignItems: "center", gap: "4px" }, activeSortButton: { background: "#61dafb", color: "#282c34" }, button: { padding: "4px 8px", border: "1px solid #61dafb", borderRadius: "4px", background: "transparent", color: "#fff", cursor: "pointer" }, activeButton: { background: "#61dafb", color: "#282c34" }, resultsList: { listStyle: "none", padding: "0", margin: "0" }, resultsItem: { padding: "4px 0", textAlign: "center", fontSize: "14px", cursor: "pointer", transition: "background-color 0.2s", borderRadius: "4px" }, resultsItemHover: { backgroundColor: "rgba(97, 218, 251, 0.2)" }, resultsItemDisabled: { backgroundColor: "rgba(220, 53, 69, 0.2)" }, resultsDiv: { height: "auto", overflowY: "visible", marginTop: "8px" }, settingsButton: { position: "absolute", top: "10px", right: "10px", padding: "4px 8px", border: "1px solid #61dafb", borderRadius: "4px", background: "transparent", color: "#fff", cursor: "pointer", fontSize: "12px" }, settingsPanel: { position: "fixed", top: "10px", left: "10px", backgroundColor: "rgba(40, 44, 52, 0.9)", border: "2px solid #61dafb", borderRadius: "8px", padding: "12px", zIndex: "2147483647", width: "220px", color: "#fff", fontFamily: "sans-serif", fontSize: "12px", cursor: "move", boxShadow: "0px 4px 12px rgba(0,0,0,0.5)" }, settingsGroup: { marginBottom: "8px", display: "flex", flexDirection: "column" }, settingsLabel: { display: "block", marginBottom: "2px", color: "#61dafb", fontSize: "11px" }, settingsInputGroup: { display: "flex", gap: "8px", alignItems: "center" }, settingsInput: { width: "50px", padding: "2px 4px", backgroundColor: "rgba(255, 255, 255, 0.1)", border: "1px solid #61dafb", borderRadius: "4px", color: "#fff", fontSize: "11px" }, settingsSlider: { flex: 1, height: "4px", WebkitAppearance: "none", background: "rgba(97, 218, 251, 0.2)", borderRadius: "2px", outline: "none" } }; function applyStyles(element, styleObj) { Object.assign(element.style, styleObj); } window.BPS.styles = styles, window.BPS.applyStyles = applyStyles; })(); // src/ui/dragResize.js window.BPS = window.BPS || {}; (function() { "use strict"; let styles = window.BPS.styles, applyStyles = window.BPS.applyStyles; function makeDraggable(element) { let isDragging = !1, offsetX = 0, offsetY = 0; element.addEventListener("mousedown", (e) => { let tag = e.target.tagName.toLowerCase(); tag === "button" || tag === "input" || (isDragging = !0, offsetX = e.clientX - element.offsetLeft, offsetY = e.clientY - element.offsetTop, e.preventDefault()); }), document.addEventListener("mousemove", (e) => { if (!isDragging) return; e.preventDefault(); let x = e.clientX - offsetX, y = e.clientY - offsetY; element.style.left = Math.max(0, Math.min(window.innerWidth - element.offsetWidth, x)) + "px", element.style.top = Math.max(0, Math.min(window.innerHeight - element.offsetHeight, y)) + "px"; }), document.addEventListener("mouseup", () => { isDragging = !1; }); } function setupDraggableResize(panel) { [ { corner: "nw", top: "-10px", left: "-10px", cursor: "nw-resize" }, { corner: "ne", top: "-10px", right: "-10px", cursor: "ne-resize" }, { corner: "se", bottom: "-10px", right: "-10px", cursor: "se-resize" }, { corner: "sw", bottom: "-10px", left: "-10px", cursor: "sw-resize" } ].forEach((pos) => { let handle = document.createElement("div"); handle.className = `resize-handle ${pos.corner}`, applyStyles(handle, { ...styles.resizeHandle, ...pos }); let dot = document.createElement("div"); applyStyles(dot, styles.resizeDot), handle.appendChild(dot), panel.appendChild(handle); }), [ { edge: "n", top: "-5px", left: "20px", right: "20px", height: "10px", cursor: "ns-resize" }, { edge: "s", bottom: "-5px", left: "20px", right: "20px", height: "10px", cursor: "ns-resize" }, { edge: "e", top: "20px", right: "-5px", bottom: "20px", width: "10px", cursor: "ew-resize" }, { edge: "w", top: "20px", left: "-5px", bottom: "20px", width: "10px", cursor: "ew-resize" } ].forEach((pos) => { let edge = document.createElement("div"); edge.className = `resize-edge ${pos.edge}`, applyStyles(edge, { ...styles.resizeEdge, ...pos }), panel.appendChild(edge); }); let draggingPanel = !1, offsetX = 0, offsetY = 0; panel.addEventListener("mousedown", (e) => { e.target.classList.contains("resize-handle") || e.target.classList.contains("resize-edge") || (draggingPanel = !0, offsetX = e.clientX - panel.getBoundingClientRect().left, offsetY = e.clientY - panel.getBoundingClientRect().top, e.preventDefault()); }), panel.addEventListener("mousemove", (e) => { if (!draggingPanel) return; let newLeft = e.clientX - offsetX, newTop = e.clientY - offsetY; panel.style.left = Math.max(0, Math.min(window.innerWidth - panel.offsetWidth, newLeft)) + "px", panel.style.top = Math.max(0, Math.min(window.innerHeight - panel.offsetHeight, newTop)) + "px"; }), panel.addEventListener("mouseup", () => { draggingPanel = !1; }), panel.addEventListener("mouseleave", () => { draggingPanel = !1; }); let resizing = !1, currentResizer = null, startX, startY, startWidth, startHeight, panelLeft, panelTop; [ ...panel.querySelectorAll(".resize-handle"), ...panel.querySelectorAll(".resize-edge") ].forEach((r) => { r.addEventListener("mousedown", (e) => { resizing = !0, currentResizer = r, startX = e.clientX, startY = e.clientY; let rect = panel.getBoundingClientRect(); startWidth = rect.width, startHeight = rect.height, panelLeft = rect.left, panelTop = rect.top, e.preventDefault(), e.stopPropagation(); }); }), document.addEventListener("mousemove", (e) => { if (!resizing || !currentResizer) return; let dx = e.clientX - startX, dy = e.clientY - startY, maxW = parseInt(styles.panel.maxWidth, 10) || 500, minW = parseInt(styles.panel.minWidth, 10) || 200, maxH = parseInt(styles.panel.maxHeight, 10) || 800, minH = parseInt(styles.panel.minHeight, 10) || 150, newW = startWidth, newH = startHeight, newL = panelLeft, newT = panelTop, direction = currentResizer.classList[1]; if (currentResizer.classList.contains("resize-handle")) switch (direction) { case "nw": newW = startWidth - dx, newH = startHeight - dy, newL = panelLeft + (startWidth - newW), newT = panelTop + (startHeight - newH); break; case "ne": newW = startWidth + dx, newH = startHeight - dy, newT = panelTop + (startHeight - newH); break; case "se": newW = startWidth + dx, newH = startHeight + dy; break; case "sw": newW = startWidth - dx, newH = startHeight + dy, newL = panelLeft + (startWidth - newW); break; } else switch (direction) { case "n": newH = startHeight - dy, newT = panelTop + (startHeight - newH); break; case "s": newH = startHeight + dy; break; case "e": newW = startWidth + dx; break; case "w": newW = startWidth - dx, newL = panelLeft + (startWidth - newW); break; } newW = Math.min(maxW, Math.max(minW, newW)), newH = Math.min(maxH, Math.max(minH, newH)), newL = Math.min(window.innerWidth - newW, Math.max(0, newL)), newT = Math.min(window.innerHeight - newH, Math.max(0, newT)), panel.style.width = newW + "px", panel.style.height = newH + "px", panel.style.left = newL + "px", panel.style.top = newT + "px"; }), document.addEventListener("mouseup", () => { resizing = !1, currentResizer = null; }); } window.BPS.makeDraggable = makeDraggable, window.BPS.setupDraggableResize = setupDraggableResize; })(); // src/ui/settings.js window.BPS = window.BPS || {}; (function() { "use strict"; let styles = window.BPS.styles, applyStyles = window.BPS.applyStyles, TYPER_CONFIG = window.BPS.TYPER_CONFIG, saveSettings = window.BPS.saveSettings, loadSavedSettings = window.BPS.loadSavedSettings, makeDraggable = window.BPS.makeDraggable; function createSettingsPanel() { let panel = document.createElement("div"); panel.id = "typerSettingsPanel", applyStyles(panel, styles.settingsPanel), panel.style.display = "none", makeDraggable(panel); let header = document.createElement("div"); applyStyles(header, { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "10px" }); let title = document.createElement("h3"); title.textContent = "Typer Settings", title.style.margin = "0", title.style.color = "#61dafb", title.style.fontSize = "14px", header.appendChild(title); let resetBtn = document.createElement("button"); return resetBtn.textContent = "\u21BA", resetBtn.title = "Reset to defaults", applyStyles(resetBtn, { ...styles.button, padding: "2px 6px", fontSize: "14px", marginLeft: "8px", backgroundColor: "transparent" }), resetBtn.onmouseenter = () => { resetBtn.style.backgroundColor = "rgba(97, 218, 251, 0.2)"; }, resetBtn.onmouseleave = () => { resetBtn.style.backgroundColor = "transparent"; }, resetBtn.onclick = () => { Object.assign(TYPER_CONFIG, JSON.parse(JSON.stringify({ baseDelay: 60, distanceMultiplier: 12.5, minDelay: 15, delayVariation: 0.2, typoChance: 2, typoNoticeDelay: { mean: 250, stdDev: 60 }, typoBackspaceDelay: { mean: 100, stdDev: 40 }, typoRecoveryDelay: { mean: 200, stdDev: 50 } }))), saveSettings(), refreshSettingsPanel(panel); }, header.appendChild(resetBtn), panel.appendChild(header), panel.appendChild(createSettingInput("Base Delay (ms)", "baseDelay", TYPER_CONFIG.baseDelay, 0, 100, 1)), panel.appendChild(createSettingInput("Distance Multiplier", "distanceMultiplier", TYPER_CONFIG.distanceMultiplier, 0, 20, 0.1)), panel.appendChild(createSettingInput("Minimum Delay (ms)", "minDelay", TYPER_CONFIG.minDelay, 0, 50, 1)), panel.appendChild(createSettingInput("Delay Variation", "delayVariation", TYPER_CONFIG.delayVariation, 0, 1, 0.01)), panel.appendChild(createSettingInput("Typo Chance (%)", "typoChance", TYPER_CONFIG.typoChance, 0, 10, 0.1)), panel.appendChild(createSettingInput("Notice Delay (ms)", "typoNoticeDelay.mean", TYPER_CONFIG.typoNoticeDelay.mean, 0, 1e3, 10)), panel.appendChild(createSettingInput("Notice Variation", "typoNoticeDelay.stdDev", TYPER_CONFIG.typoNoticeDelay.stdDev, 0, 200, 5)), panel.appendChild(createSettingInput("Backspace Delay (ms)", "typoBackspaceDelay.mean", TYPER_CONFIG.typoBackspaceDelay.mean, 0, 500, 10)), panel.appendChild(createSettingInput("Backspace Variation", "typoBackspaceDelay.stdDev", TYPER_CONFIG.typoBackspaceDelay.stdDev, 0, 100, 5)), panel.appendChild(createSettingInput("Recovery Delay (ms)", "typoRecoveryDelay.mean", TYPER_CONFIG.typoRecoveryDelay.mean, 0, 500, 10)), panel.appendChild(createSettingInput("Recovery Variation", "typoRecoveryDelay.stdDev", TYPER_CONFIG.typoRecoveryDelay.stdDev, 0, 100, 5)), document.body.appendChild(panel), panel; } function createSettingInput(labelText, configPath, initialValue, min, max, step) { let group = document.createElement("div"); group.className = "settingsGroup", applyStyles(group, styles.settingsGroup); let labelEl = document.createElement("label"); labelEl.textContent = labelText, applyStyles(labelEl, styles.settingsLabel), group.appendChild(labelEl); let inputGroup = document.createElement("div"); applyStyles(inputGroup, styles.settingsInputGroup); let slider = document.createElement("input"); slider.type = "range", slider.min = min, slider.max = max, slider.step = step, slider.value = initialValue, applyStyles(slider, styles.settingsSlider); let numericInput = document.createElement("input"); numericInput.type = "number", numericInput.value = initialValue, numericInput.min = min, numericInput.max = max, numericInput.step = step, applyStyles(numericInput, styles.settingsInput); let updateValue = (val) => { let keys = configPath.split("."), target = TYPER_CONFIG; for (let i = 0; i < keys.length - 1; i++) target = target[keys[i]]; target[keys[keys.length - 1]] = parseFloat(val), slider.value = val, numericInput.value = val, saveSettings(); }; return slider.addEventListener("input", () => updateValue(slider.value)), numericInput.addEventListener("change", () => updateValue(numericInput.value)), inputGroup.appendChild(slider), inputGroup.appendChild(numericInput), group.appendChild(inputGroup), group; } function refreshSettingsPanel(panel) { panel.querySelectorAll(".settingsGroup").forEach((group) => { let label = group.querySelector("label").textContent, slider = group.querySelector('input[type="range"]'), numericInput = group.querySelector('input[type="number"]'), path = { "Base Delay (ms)": "baseDelay", "Distance Multiplier": "distanceMultiplier", "Minimum Delay (ms)": "minDelay", "Delay Variation": "delayVariation", "Typo Chance (%)": "typoChance", "Notice Delay (ms)": "typoNoticeDelay.mean", "Notice Variation": "typoNoticeDelay.stdDev", "Backspace Delay (ms)": "typoBackspaceDelay.mean", "Backspace Variation": "typoBackspaceDelay.stdDev", "Recovery Delay (ms)": "typoRecoveryDelay.mean", "Recovery Variation": "typoRecoveryDelay.stdDev" }[label]; if (!path) return; let parts = path.split("."), val = TYPER_CONFIG; for (let p of parts) val = val[p]; slider.value = val, numericInput.value = val; }); } window.BPS.createSettingsPanel = createSettingsPanel, window.BPS.refreshSettingsPanel = refreshSettingsPanel; })(); // src/ui/suggester.js window.BPS = window.BPS || {}; (function() { "use strict"; let styles = window.BPS.styles, applyStyles = window.BPS.applyStyles, dictionaries = window.BPS.dictionaries, simulateTyping = window.BPS.simulateTyping, isPlayerTurn = window.BPS.isPlayerTurn, currentDictionary = "20k", currentSort = { method: "frequency", direction: "desc" }, letterScores = { e: 1, t: 2, a: 3, o: 4, i: 5, n: 6, s: 7, r: 8, h: 9, d: 10, l: 11, u: 12, c: 13, m: 14, f: 15, y: 16, w: 17, g: 18, p: 19, b: 20, v: 21, k: 22, x: 23, q: 24, j: 25, z: 26 }; function calculateRarityScore(word) { return word.toLowerCase().split("").reduce((score, letter) => score + (letterScores[letter] || 13), 0); } function sortMatches(matches) { let { method, direction } = currentSort; method === "frequency" && !dictionaries[currentDictionary].hasFrequency && (method = "length"); let sortFns = { frequency: (a, b) => b.freq - a.freq, length: (a, b) => b.word.length - a.word.length, rarity: (a, b) => calculateRarityScore(b.word) - calculateRarityScore(a.word) }, sortFn = sortFns[method] || sortFns.length; return matches.sort(direction === "desc" ? sortFn : (a, b) => -sortFn(a, b)), matches; } function suggestWords(syllable) { let resultsDiv = document.getElementById("bombPartyWordSuggesterResults"); if (!resultsDiv) return; if (!syllable) { resultsDiv.textContent = "(Waiting for syllable...)"; return; } let dictObj = dictionaries[currentDictionary]; if (!dictObj.words.length) { resultsDiv.textContent = "Dictionary not ready yet..."; return; } let lower = syllable.toLowerCase(), matches = dictObj.words.filter((e) => e.word.toLowerCase().includes(lower)); if (!matches.length) { resultsDiv.textContent = "No suggestions found."; return; } sortMatches(matches); let ul = document.createElement("ul"); applyStyles(ul, styles.resultsList), matches.slice(0, 15).forEach(({ word }) => { let li = document.createElement("li"); applyStyles(li, styles.resultsItem), li.onmouseenter = () => { isPlayerTurn() ? applyStyles(li, styles.resultsItemHover) : applyStyles(li, styles.resultsItemDisabled); }, li.onmouseleave = () => { applyStyles(li, { backgroundColor: "transparent" }); }, li.onclick = () => { isPlayerTurn() && simulateTyping(word); }; let idx = word.toLowerCase().indexOf(lower); if (idx >= 0) { let before = word.slice(0, idx), match = word.slice(idx, idx + lower.length), after = word.slice(idx + lower.length); li.innerHTML = `${before}<span style="color:${styles.colors.highlight}">${match}</span>${after}`; } else li.textContent = word; ul.appendChild(li); }), resultsDiv.innerHTML = "", resultsDiv.appendChild(ul); } function createDictionarySizeSelector() { let container = document.createElement("div"); return applyStyles(container, styles.sizeSelector), ["5k", "20k", "273k"].forEach((dictSize) => { let btn = document.createElement("button"); btn.textContent = dictSize, applyStyles(btn, styles.button), btn.onclick = () => { if (!dictionaries[dictSize].words.length) return; currentDictionary = dictSize, [...container.querySelectorAll("button")].forEach((b) => { applyStyles(b, styles.button); }), applyStyles(btn, { ...styles.button, ...styles.activeButton }), currentSort.method === "frequency" && !dictionaries[dictSize].hasFrequency && (currentSort.method = "length", currentSort.direction = "desc"); let sEl = document.querySelector(".syllable"); sEl && suggestWords(sEl.textContent.trim()); }, btn.onmousedown = (e) => e.stopPropagation(), container.appendChild(btn); }), container; } function createSortControls() { let sortControls = document.createElement("div"); return applyStyles(sortControls, styles.sortControls), Object.entries({ frequency: "Freq", length: "Len", rarity: "Rare" }).forEach(([method, label]) => { let btn = document.createElement("button"); btn.textContent = label + " \u2191", applyStyles(btn, styles.sortButton); let isAscending = !0; btn.onclick = () => { if (method === "frequency" && !dictionaries[currentDictionary].hasFrequency) return; currentSort.method === method ? isAscending = !isAscending : isAscending = !0, currentSort.method = method, currentSort.direction = isAscending ? "desc" : "asc", [...sortControls.querySelectorAll("button")].forEach((b) => { applyStyles(b, styles.sortButton), b.textContent = b.textContent.replace(/[↑↓]/, "\u2191"); }), applyStyles(btn, { ...styles.sortButton, ...styles.activeSortButton }), btn.textContent = `${label} ${isAscending ? "\u2193" : "\u2191"}`; let sEl = document.querySelector(".syllable"); sEl && suggestWords(sEl.textContent.trim()); }, btn.onmousedown = (e) => e.stopPropagation(), sortControls.appendChild(btn); }), sortControls; } function getCurrentDictionary() { return currentDictionary; } function getCurrentSort() { return currentSort; } window.BPS.suggestWords = suggestWords, window.BPS.createDictionarySizeSelector = createDictionarySizeSelector, window.BPS.createSortControls = createSortControls, window.BPS.getCurrentDictionary = getCurrentDictionary, window.BPS.getCurrentSort = getCurrentSort; })(); // src/ui/main.js window.BPS = window.BPS || {}; (function() { "use strict"; let styles = window.BPS.styles, applyStyles = window.BPS.applyStyles, setupDraggableResize = window.BPS.setupDraggableResize, createSettingsPanel = window.BPS.createSettingsPanel, createDictionarySizeSelector = window.BPS.createDictionarySizeSelector, createSortControls = window.BPS.createSortControls; function createUI() { let panel = document.createElement("div"); panel.id = "bombPartyWordSuggesterPanel", applyStyles(panel, styles.panel), setupDraggableResize(panel); let content = document.createElement("div"); content.id = "bombPartyWordSuggesterContent", panel.appendChild(content); let sizeSelector = createDictionarySizeSelector(); content.appendChild(sizeSelector); let sortControls = createSortControls(); content.appendChild(sortControls); let resultsDiv = document.createElement("div"); resultsDiv.id = "bombPartyWordSuggesterResults", applyStyles(resultsDiv, styles.resultsDiv), resultsDiv.textContent = "(Waiting for syllable...)", content.appendChild(resultsDiv); let settingsPanel = createSettingsPanel(), settingsButton = document.createElement("button"); settingsButton.textContent = "\u2699\uFE0F", applyStyles(settingsButton, styles.settingsButton), settingsButton.onclick = () => { settingsPanel.style.display = settingsPanel.style.display === "none" ? "block" : "none"; }, settingsButton.onmousedown = (e) => e.stopPropagation(), panel.appendChild(settingsButton), document.body.appendChild(panel); let dictButtons = sizeSelector.querySelectorAll("button"); dictButtons[1] && applyStyles(dictButtons[1], { ...styles.button, ...styles.activeButton }); let sortButtons = sortControls.querySelectorAll("button"); sortButtons[0] && (applyStyles(sortButtons[0], { ...styles.sortButton, ...styles.activeSortButton }), sortButtons[0].textContent = "Freq \u2193"); } function initScript() { createUI(), window.BPS.setupSyllableObserver(); } window.BPS.initScript = initScript; })(); // src/ui/observer.js window.BPS = window.BPS || {}; (function() { "use strict"; let syllableObserver = null, suggestWords = window.BPS.suggestWords; function setupSyllableObserver() { if (syllableObserver) return; syllableObserver = new MutationObserver((mutations) => { for (let m of mutations) if (m.type === "childList" || m.type === "characterData") { let text = m.target.textContent.trim(); text && suggestWords(text); } }); function waitForSyllable() { let el = document.querySelector(".syllable"); el ? (syllableObserver.observe(el, { childList: !0, characterData: !0, subtree: !0 }), el.textContent.trim() && suggestWords(el.textContent.trim())) : setTimeout(waitForSyllable, 1e3); } waitForSyllable(); } window.BPS.setupSyllableObserver = setupSyllableObserver; })(); // src/index.js (function() { "use strict"; typeof window.BPS < "u" && window.BPS.loadAllDictionaries().then(() => { window.BPS.initScript(); }); })(); })();