Latest commit

History

164 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

WebBoggle

Netlify Status

The aim of this application is to solve the board game Boggle(TM)

My solution recursively searches the entire board, but significant speed optimisations were achieved by pruning search branches with the use of a prepared dictionary, built with a linked-list type data structure (not quite a linked list as the data structure looks like WTNode{data: str, isWord: boolean, children ={"A" : nodeA, "B": nodeB, ...}}).

Not only are the words returned, but also coordinates (as though the Boggle board is represented in a 2D array, ie 1,0 for 1st letter on the 2nd row). This is so that a graphical representation can be made of the words on the board. The data structure is a dictionary { "WORD": ["1,0", "0,1", "0,2", "1,3"] ...}.

WordTree generation, from a word list of 279,496 words, takes around 2s. Note, this is done once per application run, not once per Board search.

Typical Board search speed on my laptop (CPU: i7-8550u, SSD, RAM: 16GB) is in the region of 1-7ms. Over 10,000 randomly generated boards, the average speed was 1.67ms, max 7.999ms, min 0.0ms (ie, too small for python to count)

On my recently purchased laptop (CPU: R7-4800u, Nvme SSD, RAM: 16GB) 10,000 randomly generated boards take around 1.45ms (max: 6.000ms, min: 0.0ms)

Optimisations

I implemented several optimisations in my code. I aim to list and explain them all here.

  1. Once a word is found, the final node in the word has its isWord property is set to False. This operation results in "voided words", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to prevent words being "found" again and producing duplicates. This optimisation does away with the need for the duplicates_analysis function (found in solver.py)

  2. Once a word is found I try to determine if the node should be voided (this is different to the isWord property) by looking at all children of the the final node in the word, and if there are no children or all children are themselves void, then this node is voided as well. This operation results in "voided nodes", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board, potentially saving time going down this path

  3. Similar to optimisation #2, when leaving a node, I check if the node has no children or all it's children are themselves void, in which case this node is voided. Similarly to #2, voided nodes are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board potentially saving time going down this path

Optimisation #2 and #3 dont show a notable speed improvement on a regular 4x4 boggle board, but I think that this code is optimal and perhaps could be of use in larger NxN boards

Run

On Windows:

python -m venv venv
.\venv\Scripts\activate
pip install --upgrade pip
pip install -r requirements.txt
.\reset_db.cmd
flask run

On Linux:

python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
source reset_db.cmd
python app.py

Acknowledgments:

  • Segments of my code were forked from or modified based on, code written by Daniel Samet
  • The web app, including HTML, and backend code, is almost entirely the work of Daniel Samet (thanks!)
  • Thanks Dr Arman Khouzani for the Linux install scripts! (I was using an IDE, and overlooked this 😯)

About

Boggle Solver (with web display....)

Resources

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

164 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

WebBoggle

Netlify Status

The aim of this application is to solve the board game Boggle(TM)

My solution recursively searches the entire board, but significant speed optimisations were achieved by pruning search branches with the use of a prepared dictionary, built with a linked-list type data structure (not quite a linked list as the data structure looks like WTNode{data: str, isWord: boolean, children ={"A" : nodeA, "B": nodeB, ...}}).

Not only are the words returned, but also coordinates (as though the Boggle board is represented in a 2D array, ie 1,0 for 1st letter on the 2nd row). This is so that a graphical representation can be made of the words on the board. The data structure is a dictionary { "WORD": ["1,0", "0,1", "0,2", "1,3"] ...}.

WordTree generation, from a word list of 279,496 words, takes around 2s. Note, this is done once per application run, not once per Board search.

Typical Board search speed on my laptop (CPU: i7-8550u, SSD, RAM: 16GB) is in the region of 1-7ms. Over 10,000 randomly generated boards, the average speed was 1.67ms, max 7.999ms, min 0.0ms (ie, too small for python to count)

On my recently purchased laptop (CPU: R7-4800u, Nvme SSD, RAM: 16GB) 10,000 randomly generated boards take around 1.45ms (max: 6.000ms, min: 0.0ms)

Optimisations

I implemented several optimisations in my code. I aim to list and explain them all here.

  1. Once a word is found, the final node in the word has its isWord property is set to False. This operation results in "voided words", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to prevent words being "found" again and producing duplicates. This optimisation does away with the need for the duplicates_analysis function (found in solver.py)

  2. Once a word is found I try to determine if the node should be voided (this is different to the isWord property) by looking at all children of the the final node in the word, and if there are no children or all children are themselves void, then this node is voided as well. This operation results in "voided nodes", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board, potentially saving time going down this path

  3. Similar to optimisation #2, when leaving a node, I check if the node has no children or all it's children are themselves void, in which case this node is voided. Similarly to #2, voided nodes are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board potentially saving time going down this path

Optimisation #2 and #3 dont show a notable speed improvement on a regular 4x4 boggle board, but I think that this code is optimal and perhaps could be of use in larger NxN boards

Run

On Windows:

python -m venv venv
.\venv\Scripts\activate
pip install --upgrade pip
pip install -r requirements.txt
.\reset_db.cmd
flask run

On Linux:

python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
source reset_db.cmd
python app.py

Acknowledgments:

  • Segments of my code were forked from or modified based on, code written by Daniel Samet
  • The web app, including HTML, and backend code, is almost entirely the work of Daniel Samet (thanks!)
  • Thanks Dr Arman Khouzani for the Linux install scripts! (I was using an IDE, and overlooked this 😯)

About

Boggle Solver (with web display....)

Resources

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

164 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

WebBoggle

Netlify Status

The aim of this application is to solve the board game Boggle(TM)

My solution recursively searches the entire board, but significant speed optimisations were achieved by pruning search branches with the use of a prepared dictionary, built with a linked-list type data structure (not quite a linked list as the data structure looks like WTNode{data: str, isWord: boolean, children ={"A" : nodeA, "B": nodeB, ...}}).

Not only are the words returned, but also coordinates (as though the Boggle board is represented in a 2D array, ie 1,0 for 1st letter on the 2nd row). This is so that a graphical representation can be made of the words on the board. The data structure is a dictionary { "WORD": ["1,0", "0,1", "0,2", "1,3"] ...}.

WordTree generation, from a word list of 279,496 words, takes around 2s. Note, this is done once per application run, not once per Board search.

Typical Board search speed on my laptop (CPU: i7-8550u, SSD, RAM: 16GB) is in the region of 1-7ms. Over 10,000 randomly generated boards, the average speed was 1.67ms, max 7.999ms, min 0.0ms (ie, too small for python to count)

On my recently purchased laptop (CPU: R7-4800u, Nvme SSD, RAM: 16GB) 10,000 randomly generated boards take around 1.45ms (max: 6.000ms, min: 0.0ms)

Optimisations

I implemented several optimisations in my code. I aim to list and explain them all here.

  1. Once a word is found, the final node in the word has its isWord property is set to False. This operation results in "voided words", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to prevent words being "found" again and producing duplicates. This optimisation does away with the need for the duplicates_analysis function (found in solver.py)

  2. Once a word is found I try to determine if the node should be voided (this is different to the isWord property) by looking at all children of the the final node in the word, and if there are no children or all children are themselves void, then this node is voided as well. This operation results in "voided nodes", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board, potentially saving time going down this path

  3. Similar to optimisation #2, when leaving a node, I check if the node has no children or all it's children are themselves void, in which case this node is voided. Similarly to #2, voided nodes are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board potentially saving time going down this path

Optimisation #2 and #3 dont show a notable speed improvement on a regular 4x4 boggle board, but I think that this code is optimal and perhaps could be of use in larger NxN boards

Run

On Windows:

python -m venv venv
.\venv\Scripts\activate
pip install --upgrade pip
pip install -r requirements.txt
.\reset_db.cmd
flask run

On Linux:

python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
source reset_db.cmd
python app.py

Acknowledgments:

  • Segments of my code were forked from or modified based on, code written by Daniel Samet
  • The web app, including HTML, and backend code, is almost entirely the work of Daniel Samet (thanks!)
  • Thanks Dr Arman Khouzani for the Linux install scripts! (I was using an IDE, and overlooked this 😯)

About

Boggle Solver (with web display....)

Resources

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

164 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

WebBoggle

Netlify Status

The aim of this application is to solve the board game Boggle(TM)

My solution recursively searches the entire board, but significant speed optimisations were achieved by pruning search branches with the use of a prepared dictionary, built with a linked-list type data structure (not quite a linked list as the data structure looks like WTNode{data: str, isWord: boolean, children ={"A" : nodeA, "B": nodeB, ...}}).

Not only are the words returned, but also coordinates (as though the Boggle board is represented in a 2D array, ie 1,0 for 1st letter on the 2nd row). This is so that a graphical representation can be made of the words on the board. The data structure is a dictionary { "WORD": ["1,0", "0,1", "0,2", "1,3"] ...}.

WordTree generation, from a word list of 279,496 words, takes around 2s. Note, this is done once per application run, not once per Board search.

Typical Board search speed on my laptop (CPU: i7-8550u, SSD, RAM: 16GB) is in the region of 1-7ms. Over 10,000 randomly generated boards, the average speed was 1.67ms, max 7.999ms, min 0.0ms (ie, too small for python to count)

On my recently purchased laptop (CPU: R7-4800u, Nvme SSD, RAM: 16GB) 10,000 randomly generated boards take around 1.45ms (max: 6.000ms, min: 0.0ms)

Optimisations

I implemented several optimisations in my code. I aim to list and explain them all here.

  1. Once a word is found, the final node in the word has its isWord property is set to False. This operation results in "voided words", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to prevent words being "found" again and producing duplicates. This optimisation does away with the need for the duplicates_analysis function (found in solver.py)

  2. Once a word is found I try to determine if the node should be voided (this is different to the isWord property) by looking at all children of the the final node in the word, and if there are no children or all children are themselves void, then this node is voided as well. This operation results in "voided nodes", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board, potentially saving time going down this path

  3. Similar to optimisation #2, when leaving a node, I check if the node has no children or all it's children are themselves void, in which case this node is voided. Similarly to #2, voided nodes are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board potentially saving time going down this path

Optimisation #2 and #3 dont show a notable speed improvement on a regular 4x4 boggle board, but I think that this code is optimal and perhaps could be of use in larger NxN boards

Run

On Windows:

python -m venv venv
.\venv\Scripts\activate
pip install --upgrade pip
pip install -r requirements.txt
.\reset_db.cmd
flask run

On Linux:

python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
source reset_db.cmd
python app.py

Acknowledgments:

  • Segments of my code were forked from or modified based on, code written by Daniel Samet
  • The web app, including HTML, and backend code, is almost entirely the work of Daniel Samet (thanks!)
  • Thanks Dr Arman Khouzani for the Linux install scripts! (I was using an IDE, and overlooked this 😯)

About

Boggle Solver (with web display....)

Resources

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

164 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

WebBoggle

Netlify Status

The aim of this application is to solve the board game Boggle(TM)

My solution recursively searches the entire board, but significant speed optimisations were achieved by pruning search branches with the use of a prepared dictionary, built with a linked-list type data structure (not quite a linked list as the data structure looks like WTNode{data: str, isWord: boolean, children ={"A" : nodeA, "B": nodeB, ...}}).

Not only are the words returned, but also coordinates (as though the Boggle board is represented in a 2D array, ie 1,0 for 1st letter on the 2nd row). This is so that a graphical representation can be made of the words on the board. The data structure is a dictionary { "WORD": ["1,0", "0,1", "0,2", "1,3"] ...}.

WordTree generation, from a word list of 279,496 words, takes around 2s. Note, this is done once per application run, not once per Board search.

Typical Board search speed on my laptop (CPU: i7-8550u, SSD, RAM: 16GB) is in the region of 1-7ms. Over 10,000 randomly generated boards, the average speed was 1.67ms, max 7.999ms, min 0.0ms (ie, too small for python to count)

On my recently purchased laptop (CPU: R7-4800u, Nvme SSD, RAM: 16GB) 10,000 randomly generated boards take around 1.45ms (max: 6.000ms, min: 0.0ms)

Optimisations

I implemented several optimisations in my code. I aim to list and explain them all here.

  1. Once a word is found, the final node in the word has its isWord property is set to False. This operation results in "voided words", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to prevent words being "found" again and producing duplicates. This optimisation does away with the need for the duplicates_analysis function (found in solver.py)

  2. Once a word is found I try to determine if the node should be voided (this is different to the isWord property) by looking at all children of the the final node in the word, and if there are no children or all children are themselves void, then this node is voided as well. This operation results in "voided nodes", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board, potentially saving time going down this path

  3. Similar to optimisation #2, when leaving a node, I check if the node has no children or all it's children are themselves void, in which case this node is voided. Similarly to #2, voided nodes are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board potentially saving time going down this path

Optimisation #2 and #3 dont show a notable speed improvement on a regular 4x4 boggle board, but I think that this code is optimal and perhaps could be of use in larger NxN boards

Run

On Windows:

python -m venv venv
.\venv\Scripts\activate
pip install --upgrade pip
pip install -r requirements.txt
.\reset_db.cmd
flask run

On Linux:

python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
source reset_db.cmd
python app.py

Acknowledgments:

  • Segments of my code were forked from or modified based on, code written by Daniel Samet
  • The web app, including HTML, and backend code, is almost entirely the work of Daniel Samet (thanks!)
  • Thanks Dr Arman Khouzani for the Linux install scripts! (I was using an IDE, and overlooked this 😯)

About

Boggle Solver (with web display....)

Resources

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

164 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

WebBoggle

Netlify Status

The aim of this application is to solve the board game Boggle(TM)

My solution recursively searches the entire board, but significant speed optimisations were achieved by pruning search branches with the use of a prepared dictionary, built with a linked-list type data structure (not quite a linked list as the data structure looks like WTNode{data: str, isWord: boolean, children ={"A" : nodeA, "B": nodeB, ...}}).

Not only are the words returned, but also coordinates (as though the Boggle board is represented in a 2D array, ie 1,0 for 1st letter on the 2nd row). This is so that a graphical representation can be made of the words on the board. The data structure is a dictionary { "WORD": ["1,0", "0,1", "0,2", "1,3"] ...}.

WordTree generation, from a word list of 279,496 words, takes around 2s. Note, this is done once per application run, not once per Board search.

Typical Board search speed on my laptop (CPU: i7-8550u, SSD, RAM: 16GB) is in the region of 1-7ms. Over 10,000 randomly generated boards, the average speed was 1.67ms, max 7.999ms, min 0.0ms (ie, too small for python to count)

On my recently purchased laptop (CPU: R7-4800u, Nvme SSD, RAM: 16GB) 10,000 randomly generated boards take around 1.45ms (max: 6.000ms, min: 0.0ms)

Optimisations

I implemented several optimisations in my code. I aim to list and explain them all here.

  1. Once a word is found, the final node in the word has its isWord property is set to False. This operation results in "voided words", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to prevent words being "found" again and producing duplicates. This optimisation does away with the need for the duplicates_analysis function (found in solver.py)

  2. Once a word is found I try to determine if the node should be voided (this is different to the isWord property) by looking at all children of the the final node in the word, and if there are no children or all children are themselves void, then this node is voided as well. This operation results in "voided nodes", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board, potentially saving time going down this path

  3. Similar to optimisation #2, when leaving a node, I check if the node has no children or all it's children are themselves void, in which case this node is voided. Similarly to #2, voided nodes are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board potentially saving time going down this path

Optimisation #2 and #3 dont show a notable speed improvement on a regular 4x4 boggle board, but I think that this code is optimal and perhaps could be of use in larger NxN boards

Run

On Windows:

python -m venv venv
.\venv\Scripts\activate
pip install --upgrade pip
pip install -r requirements.txt
.\reset_db.cmd
flask run

On Linux:

python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
source reset_db.cmd
python app.py

Acknowledgments:

  • Segments of my code were forked from or modified based on, code written by Daniel Samet
  • The web app, including HTML, and backend code, is almost entirely the work of Daniel Samet (thanks!)
  • Thanks Dr Arman Khouzani for the Linux install scripts! (I was using an IDE, and overlooked this 😯)

About

Boggle Solver (with web display....)

Resources

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

164 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

WebBoggle

Netlify Status

The aim of this application is to solve the board game Boggle(TM)

My solution recursively searches the entire board, but significant speed optimisations were achieved by pruning search branches with the use of a prepared dictionary, built with a linked-list type data structure (not quite a linked list as the data structure looks like WTNode{data: str, isWord: boolean, children ={"A" : nodeA, "B": nodeB, ...}}).

Not only are the words returned, but also coordinates (as though the Boggle board is represented in a 2D array, ie 1,0 for 1st letter on the 2nd row). This is so that a graphical representation can be made of the words on the board. The data structure is a dictionary { "WORD": ["1,0", "0,1", "0,2", "1,3"] ...}.

WordTree generation, from a word list of 279,496 words, takes around 2s. Note, this is done once per application run, not once per Board search.

Typical Board search speed on my laptop (CPU: i7-8550u, SSD, RAM: 16GB) is in the region of 1-7ms. Over 10,000 randomly generated boards, the average speed was 1.67ms, max 7.999ms, min 0.0ms (ie, too small for python to count)

On my recently purchased laptop (CPU: R7-4800u, Nvme SSD, RAM: 16GB) 10,000 randomly generated boards take around 1.45ms (max: 6.000ms, min: 0.0ms)

Optimisations

I implemented several optimisations in my code. I aim to list and explain them all here.

  1. Once a word is found, the final node in the word has its isWord property is set to False. This operation results in "voided words", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to prevent words being "found" again and producing duplicates. This optimisation does away with the need for the duplicates_analysis function (found in solver.py)

  2. Once a word is found I try to determine if the node should be voided (this is different to the isWord property) by looking at all children of the the final node in the word, and if there are no children or all children are themselves void, then this node is voided as well. This operation results in "voided nodes", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board, potentially saving time going down this path

  3. Similar to optimisation #2, when leaving a node, I check if the node has no children or all it's children are themselves void, in which case this node is voided. Similarly to #2, voided nodes are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board potentially saving time going down this path

Optimisation #2 and #3 dont show a notable speed improvement on a regular 4x4 boggle board, but I think that this code is optimal and perhaps could be of use in larger NxN boards

Run

On Windows:

python -m venv venv
.\venv\Scripts\activate
pip install --upgrade pip
pip install -r requirements.txt
.\reset_db.cmd
flask run

On Linux:

python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
source reset_db.cmd
python app.py

Acknowledgments:

  • Segments of my code were forked from or modified based on, code written by Daniel Samet
  • The web app, including HTML, and backend code, is almost entirely the work of Daniel Samet (thanks!)
  • Thanks Dr Arman Khouzani for the Linux install scripts! (I was using an IDE, and overlooked this 😯)

About

Boggle Solver (with web display....)

Resources

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

164 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

WebBoggle

Netlify Status

The aim of this application is to solve the board game Boggle(TM)

My solution recursively searches the entire board, but significant speed optimisations were achieved by pruning search branches with the use of a prepared dictionary, built with a linked-list type data structure (not quite a linked list as the data structure looks like WTNode{data: str, isWord: boolean, children ={"A" : nodeA, "B": nodeB, ...}}).

Not only are the words returned, but also coordinates (as though the Boggle board is represented in a 2D array, ie 1,0 for 1st letter on the 2nd row). This is so that a graphical representation can be made of the words on the board. The data structure is a dictionary { "WORD": ["1,0", "0,1", "0,2", "1,3"] ...}.

WordTree generation, from a word list of 279,496 words, takes around 2s. Note, this is done once per application run, not once per Board search.

Typical Board search speed on my laptop (CPU: i7-8550u, SSD, RAM: 16GB) is in the region of 1-7ms. Over 10,000 randomly generated boards, the average speed was 1.67ms, max 7.999ms, min 0.0ms (ie, too small for python to count)

On my recently purchased laptop (CPU: R7-4800u, Nvme SSD, RAM: 16GB) 10,000 randomly generated boards take around 1.45ms (max: 6.000ms, min: 0.0ms)

Optimisations

I implemented several optimisations in my code. I aim to list and explain them all here.

  1. Once a word is found, the final node in the word has its isWord property is set to False. This operation results in "voided words", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to prevent words being "found" again and producing duplicates. This optimisation does away with the need for the duplicates_analysis function (found in solver.py)

  2. Once a word is found I try to determine if the node should be voided (this is different to the isWord property) by looking at all children of the the final node in the word, and if there are no children or all children are themselves void, then this node is voided as well. This operation results in "voided nodes", which are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board, potentially saving time going down this path

  3. Similar to optimisation #2, when leaving a node, I check if the node has no children or all it's children are themselves void, in which case this node is voided. Similarly to #2, voided nodes are tracked so that they can be reset for every new board search

    Why? This optimisation is done to eliminate nodes that aren't words and don't lead to words from later searches on the same board potentially saving time going down this path

Optimisation #2 and #3 dont show a notable speed improvement on a regular 4x4 boggle board, but I think that this code is optimal and perhaps could be of use in larger NxN boards

Run

On Windows:

python -m venv venv
.\venv\Scripts\activate
pip install --upgrade pip
pip install -r requirements.txt
.\reset_db.cmd
flask run

On Linux:

python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
source reset_db.cmd
python app.py

Acknowledgments:

  • Segments of my code were forked from or modified based on, code written by Daniel Samet
  • The web app, including HTML, and backend code, is almost entirely the work of Daniel Samet (thanks!)
  • Thanks Dr Arman Khouzani for the Linux install scripts! (I was using an IDE, and overlooked this 😯)

About

Boggle Solver (with web display....)

Resources

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages