Solution code
const CURRENT_CELL = 'current_cell', CURRENT_CELL_COLOR = 'blue';
const CURRENT_TRIE_NODE = 'current_trie_node', CURRENT_TRIE_NODE_COLOR = 'orange';
const NEXT_NEIGHBOUR = 'next_neighbour', NEXT_NEIGHBOUR_COLOR = 'gold';
class TrieNode {
constructor() {
this.children = new VisChildrenObject();
this.wordIndex = null;
}
}
function visMainFunction(boardInput, words) {
initClassProperties();
explanationAndColorSchemaNotifications();
const board = new VisArray2D(...boardInput.map(row => new VisArray(...row))).options({ label: 'Board' });
const root = new TrieNode();
const tree = new VisDictionaryTree(root, { dataPropName: 'wordIndex' });
buildTrie(words, root);
const result = new VisArray();
const rows = board.length;
const cols = board[0].length;
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
startPartialBatch();
setCurrentTrieNode(root, tree);
setCurrentCell(row, col, board);
notification(`π Starting DFS from ${makeColoredSpan('board cell', CURRENT_CELL_COLOR)} at [${row},${col}] containing letter <b>${board[row][col]}</b>.`);
endBatch();
dfs(row, col, root);
}
}
notification(`π <b>Search Complete!</b> Words found: <b>${result.join(', ')}</b>.`);
return result;
function buildTrie(words, root) {
notification(`π <b>Building the Trie</b> to store all dictionary words by their indices for efficient prefix matching.`);
for (let wordIndex = 0; wordIndex < words.length; wordIndex++) {
const word = words[wordIndex];
startPartialBatch();
notification(`βοΈ <b>Inserting word:</b> <b>${word}</b> (index ${wordIndex}) into the Trie.`);
let node = root;
for (const char of word) {
if (!node.children[char]) {
notification(`β No child for <b>${char}</b> at ${makeColoredSpan('current trie node', CURRENT_TRIE_NODE_COLOR)}. Creating a new node for <b>${char}</b> and proceeding to it.`);
node.children[char] = new TrieNode();
} else {
notification(`β‘οΈ Child for <b>${char}</b> already exists. Moving to the corresponding ${makeColoredSpan('trie node', CURRENT_TRIE_NODE_COLOR)}.`);
}
node = node.children[char];
}
notification(`π Reached the end of <b>${word}</b>. Storing its <b>index (${wordIndex})</b> at the ${makeColoredSpan('current trie node', CURRENT_TRIE_NODE_COLOR)} since the path from root to this node yields the full word.`);
node.wordIndex = wordIndex;
endBatch();
}
}
function dfs(row, col, node) {
const char = board[row][col];
startPartialBatch();
setCurrentCell(row, col, board);
notification(`
π Exploring ${makeColoredSpan('board cell', CURRENT_CELL_COLOR)} at [${row},${col}] with letter <b>${char}</b>.
Checking whether this letter can extend the <b>current Trie prefix</b>.
`);
node = node.children[char];
if (!node) {
notification(`
β No matching child found for <b>${char}</b> in the ${makeColoredSpan('current trie node', CURRENT_TRIE_NODE_COLOR)}.
This prefix does not exist in the dictionary β <b>pruning</b> this DFS path.
`);
endBatch();
return;
}
notification(`
β
Found a matching child for <b>${char}</b> in the Trie.
Proceeding to this child by assigning the ${makeColoredSpan('current trie node', CURRENT_TRIE_NODE_COLOR)} to it.
`);
setCurrentTrieNode(node, tree);
if (node.wordIndex !== null) {
const foundWord = words[node.wordIndex];
notification(`
β
Found a complete word: <b>${foundWord}</b> (index ${node.wordIndex}) ending at the current DFS path.
Adding it to the result list.
`);
result.push(foundWord);
notification(`
π§Ή Clearing this word index stored at the ${makeColoredSpan('current trie node', CURRENT_TRIE_NODE_COLOR)}
(by assigning it to <code>null</code>) to avoid counting duplicates from other paths.
`);
node.wordIndex = null;
}
notification(`
π Temporarily marking ${makeColoredSpan('current board cell', CURRENT_CELL_COLOR)} at [${row},${col}] as visited (<b>#</b>)
so it cannot be reused within this DFS path. Exploring its valid neighbors next.
`);
board[row][col] = '#';
endBatch();
const directions = [
[0, 1],
[1, 0],
[0, -1],
[-1, 0]
];
for (const [dx, dy] of directions) {
const newRow = row + dx;
const newCol = col + dy;
if (
newRow >= 0 && newRow < rows &&
newCol >= 0 && newCol < cols &&
board[newRow][newCol] !== '#'
) {
startPartialBatch();
setCurrentCell(row, col, board);
setCurrentTrieNode(node, tree);
const boardVm = board.makeVisManagerForIndexPair(newRow, newCol).addClass(NEXT_NEIGHBOUR);
notification(`π Exploring ${makeColoredSpan('neighbor cell', NEXT_NEIGHBOUR_COLOR)} at [${newRow},${newCol}] from ${makeColoredSpan('current cell', CURRENT_CELL_COLOR)} at [${row},${col}].`);
boardVm.removeClass(NEXT_NEIGHBOUR);
endBatch();
dfs(newRow, newCol, node);
}
}
startPartialBatch();
board[row][col] = char;
notification(`
β©οΈ Finished exploring all neighbors of ${makeColoredSpan('current cell', CURRENT_CELL_COLOR)} at [${row},${col}].
Restored its original letter <b>${char}</b> to make it available again for future DFS explorations.
`);
endBatch();
}
}
let currentCellVM = null;
let currentTrieVM = null;
function setCurrentCell(row, col, board) {
startBatch();
currentCellVM && currentCellVM.removeClass(CURRENT_CELL);
currentCellVM = board.makeVisManagerForIndexPair(row, col).addClass(CURRENT_CELL);
endBatch();
}
function setCurrentTrieNode(node, tree) {
startBatch();
currentTrieVM && currentTrieVM.removeClass(CURRENT_TRIE_NODE);
currentTrieVM = tree.makeVisManagerForNode(node).addClass(CURRENT_TRIE_NODE);
endBatch();
}
function initClassProperties() {
setClassProperties(CURRENT_CELL, { borderColor: CURRENT_CELL_COLOR });
setClassProperties(NEXT_NEIGHBOUR, { backgroundColor: NEXT_NEIGHBOUR_COLOR });
setClassProperties(CURRENT_TRIE_NODE, { borderColor: CURRENT_TRIE_NODE_COLOR });
}
function explanationAndColorSchemaNotifications() {
explanationNotification();
notification(`
<b>π¨ Visual Cues (Color Schema)</b>
<ul>
<li>
${makeColoredSpan('Current board cell', CURRENT_CELL_COLOR)} β
${CURRENT_CELL_COLOR} border on the board cell currently being explored in the DFS path.
</li>
<li>
${makeColoredSpan('Current trie node', CURRENT_TRIE_NODE_COLOR)} β
${CURRENT_TRIE_NODE_COLOR} border on the Trie node representing the prefix formed so far
by the path on the board.
</li>
<li>
${makeColoredSpan('Neighbor cell candidate', NEXT_NEIGHBOUR_COLOR)} β
${NEXT_NEIGHBOUR_COLOR} background temporarily highlights a neighboring cell that is being
considered as the next DFS step from the current cell.
</li>
</ul>
`);
}