From 625efb94632f68e6da369864971606a62d956715 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Thu, 4 Dec 2025 23:02:44 +0000 Subject: [PATCH 01/14] Added base support for expressions as graphs Added methods for converting: Valid expression -> Components Components -> Postfix Prefix (**) -> Graph --- pages/index/script.js | 403 +++++++++++++++++++++++++----------------- 1 file changed, 239 insertions(+), 164 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index 3804db9..b035654 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -113,39 +113,6 @@ function checkValidInputExpression() // A number is also appended on end, which corresponds to number of missing terms in expression function evaluateExpression(expressionDict) { - let expressionEvaluation = `
`; - // For expression in dict - let terms = Object.keys(expressionDict); - for (const term of terms) - { - let coeff = expressionDict[term]; - // Compare to solution, and give correct colour - let colour = evaluateTerm(term, coeff, SOLUTION); - // Format coefficient correctly for display (e.g {'x', -1} -> -x, {'x', 5} -> 5x if first term, +5x if not) - let displayCoeff = ''; - if (coeff[0] == '-') - { - displayCoeff = (coeff == '-1') ? '-' : coeff; - } - else if (term == terms[0]) - { - displayCoeff = (coeff == '1') ? '' : coeff; - } - else - { - displayCoeff = (coeff == '1') ? '+' : `+${coeff}`; - } - // Append to evaluation a

block with correct colour - expressionEvaluation += `\\(${displayCoeff}${term}\\)`; - } - expressionEvaluation += "

"; - // Calculate number of terms missing from dict - let missingTermsCount = calculateMissingTerms(expressionDict, SOLUTION); - // Append

block with number of terms missing - expressionEvaluation += `

|| ${missingTermsCount} ||
`; - // Convert into trusted string (prevent xss attacks) - let sanitisedExpressionEvaluation = policy.createHTML(expressionEvaluation); - return sanitisedExpressionEvaluation; } // Ranks term as red, yellow or green @@ -156,14 +123,6 @@ function evaluateExpression(expressionDict) // RETURNS: str red/yellow/green function evaluateTerm(term, coeff, solution) { - // If solution doesn't contain term, red - if (!solution.hasOwnProperty(term)) - return "red"; - // If solution's coeff != coeff, yellow - if (solution[term] != coeff) - return "yellow"; - // Else, green - return "green"; } // Calculates number of terms in solution that are missing from expression @@ -171,16 +130,6 @@ function evaluateTerm(term, coeff, solution) // RETURNS: int number of missing terms function calculateMissingTerms(expressionDict, solutionDict) { - let missingCount = 0; - // Iterate over keys in solutionDict - let terms = Object.keys(solutionDict); - for (const term of terms) - { - // If not in expressionDict, +1 to missingCount - if (!expressionDict.hasOwnProperty(term)) - missingCount++; - } - return missingCount; } // Evaluates, as a %, how correct the input expression was. @@ -189,156 +138,282 @@ function calculateMissingTerms(expressionDict, solutionDict) // RETURNS: int % correctness of expression function evaluateCorrectness(expressionDict, solutionDict) { - let correctness = 0; - let terms = Object.keys(solutionDict); - // Amount correctness goes up by for each correct term / coeff - let increment = 100 / (2 * terms.length); +} - for (const term of terms) +// Expression to component list +function expressionToComponentList(expression) +{ + let Constants = ['e', "pi"]; + let exp = cleanExpression(expression); + let list = []; + let content = ""; + let type = ""; + let precedence = ""; + let commutative = true; + let i = 0; + while (i < exp.length) { - if (expressionDict.hasOwnProperty(term)) + // If a negative number that at the start, or after an open bracket/ function definition, add the implicit -1* + // e.g: -5x -> -1 * 5 x + // e.g: 10*(-x) -> 10 * ( -1 * x ) + // e.g: sin -x -> sin -1 * x + if ( + ( + list.length == 0 + || list.length > 0 && + ( + list[list.length - 1].type == "open bracket" + || list[list.length - 1].type == "function" + ) + ) + && exp[i] == '-' + ) + { + content = "-1"; + type = "number"; + + i++; + } + + // If number, find end of num and add whole num + else if (!isNaN(exp[i])) { - correctness += increment; - if (expressionDict[term] == solutionDict[term]) + let found = false; + for (let j = i; j < exp.length; j++) { - correctness += increment; + if (isNaN(exp[j])) + { + content = exp.slice(i, j); + type = "number"; + + i = j; + found = true; + break; + } + } + if (found == false) // If can't find any non-nums, then number extends to end of string + { + content = exp.slice(i); + type = "number"; + + i = exp.length; } } - } - return Math.round(correctness); -} -// Takes in dict of expression, and compares it to dict of solution. If exactly equal, player has won! -// INPUTS: dict expressionDict -// RETURNS: bool true if won, false if not -function checkWin(expressionDict) -{ + // If bracket, add to list + else if (['(', ')'].includes(exp[i])) + { + content = exp[i]; + type = exp[i] == '(' ? "open bracket" : "close bracket"; - // Check for incorrect terms - if (!isEqual(Object.keys(expressionDict), Object.keys(SOLUTION))) - { - return false; - } + i++; + } - // Check for incorrect coeffs - let terms = Object.keys(expressionDict); - for (const term of terms) - { - if (expressionDict[term] != SOLUTION[term]) + // If operator, add to list + else if (['+', '-', '/', '*', '^'].includes(exp[i])) { - return false; + content = exp[i]; + type = "operator"; + precedence = {'+': 0, '-': 0, '/': 1, '*': 1, '^': 2}[exp[i]]; + commutative = {'+': true, '-': false, '/': false, '*': true, '^': false}[exp[i]]; + i++; } - } - return true; -} -// Parses expression into a terms:coeffs dictionary mapping -// INPUT: str some valid maths expression -// RETURNS: (dict) mapping terms to coeffs -// e.g: x ^ 2 + 3 sin(4x) - 2 sin(x)cos(x) - x -// -> {x^2 : 1, sin(4x) : 3, sin(x)cos(x) : -2, x : -1} -function expressionToDict(expression) -{ - let rawExp = expression.replaceAll(" ", ""); - let i = 0; - let decomposedExpression = {}; - while (i < rawExp.length) - { - let nextTerm = termToDict(rawExp, i); - decomposedExpression[nextTerm.term] = nextTerm.coeff; - i = nextTerm.i; - } - return decomposedExpression; -} + // If x, add to list + else if (exp[i] == 'x') + { + content = 'x'; + type = "variable"; -// "3x^2" -> [x^2, 3] -function termToDict(str, i) -{ - let startTerm = -1; - let endTerm = str.length; + i++; + } - // Deal with first part of expression. If +, skip it. If -, take note and move on one. - let isNegative = false; - if (str[i] == '+') - { - i++; - } - else if (str[i] == '-') - { - i++; - isNegative = true; - } + // If other letters, treat as function/constant, add to list + else if (exp[i].toUpperCase() != exp[i].toLowerCase()) + { + let found = false; + // Go until x, constant, or non-letter found + for (let j = i; j < exp.length; j++) + { + if (exp[j] == 'x' || exp[j].toUpperCase() == exp[j].toLowerCase()) + { + content = exp.slice(i, j); - // j = start of term - for (let j = i; j < str.length; j++) - { - if (isNaN(str[j]) && str[j] != '/') + i = j; + found = true; + break; + } + } + // If for loop finishes, then function name extends to end of string + if (found == false) + { + content = exp.slice(i); + + i = exp.length; + } + + // Determine if function or constant + if (Constants.includes(content)) + { + type = "constant"; + } + else + { + type = "function"; + precedence = 0.5; + } + } + + // Add new component + let newComponent = { + content: content, + type: type, + leftNode: -1, + rightNode: -1 + }; + + if (type == "operator" || type == "function") + newComponent.precedence = precedence; + if (type == "operator") + newComponent.commutative = commutative; + + list.push(newComponent); + + // Check for implicit * signs + // If previous component is: Number, Variable, or Close Bracket + // And current component is: Open Bracket, Number, Variable, or Function + // We need a * sign between the two + // e.g 5sin(x) -> 5 * sin ( x ) + // e.g 10x -> 10 * x + // e.g (10 + x)(3 + x) -> ( 10 + x ) * ( 3 + x ) + if ( + list.length >= 2 + && ["close bracket", "number", "variable"].includes(list[list.length - 2].type) + && ["open bracket", "number", "variable", "function"].includes(list[list.length - 1].type) + ) { - startTerm = j; - break; + list.splice(-1, 0, { + content: '*', + type: "operator", + precedence: 1, + leftNode: -1, + rightNode: -1, + commutative: true + }); } + } + return list; +} - let bracketCount = 0; - - // k = end of term - for (let k = startTerm; k < str.length; k++) +// Puts the components in a list into postfix notation +// Uses modified Shunting Yard Algorithm +function componentListToPostfix(list) +{ + let infixList = []; + let operatorStack = []; + let index = 0; + // Iterate over components + while(index < list.length) { - if (str[k] == '(') + let component = list[index]; + // If number, constant, or variable, put in output + if (["number", "constant", "variable"].includes(component.type)) { - bracketCount++; + infixList.push(component); } - else if (str[k] == ')') + // If (, recurse and add to string + else if (component.type == "open bracket") { - bracketCount--; // FIXME: Won't work for bracketed terms - e.g (10x + 2x^2)^1/2' + let bracketEval = componentListToPostfix(list.slice(index+1)); + index += bracketEval.index + 1; // +1, as list indices start from 0 + infixList.push(...bracketEval.infixList); } - if (str[k] == '+' || str[k] == '-') + // If ), return + else if (component.type == "close bracket") { - if (bracketCount == 0) + infixList.push(...operatorStack.reverse()); + return { + infixList: infixList, + index: index + }; + } + // If operator or function, look at stack + else if (component.type == "operator" || component.type == "function") + { + // If higher precedence than top of stack, push + if (operatorStack.length == 0 || component.precedence > operatorStack[operatorStack.length - 1].precedence) { - endTerm = k; - break; + operatorStack.push(component); + } + // If same/lower precedence, pop all higher precedence operators + push + else + { + while (operatorStack.length > 0 && component.precedence <= operatorStack[operatorStack.length - 1].precedence) + { + infixList.push(operatorStack.pop()); + } + operatorStack.push(component); } } + index++; } - // Compose coeff - let coefficient = str.slice(i, startTerm); - if (coefficient == '') - { - coefficient = '1'; - } - if (isNegative) - { - coefficient = '-' + coefficient; - } - return { - coeff: coefficient, - term: str.slice(startTerm, endTerm), - i: endTerm - }; + // At end, unload operator stack + infixList.push(...operatorStack.reverse()); + return infixList; } -// Checks if 2 arrays are equal -function isEqual(a, b) +// Takes a list of components in postfix, and converts into tree +function postfixToTree(components, index=0) { - // Sort both arrays - a = a.sort(); - b = b.sort(); + // If at end of components, return + if (index >= components.length) + return index; - // 1. Check same length - if (a.length != b.length) - { - return false; - } + let currentComponent = components[index]; + // If number/constant, return + if (currentComponent.type == "number" || components[index].type == "constant") + return index; - // 2. Check same content - for (let i = 0; i < a.length; i++) + // If function, add next component to left node + if (currentComponent.type == "function" || currentComponent.type == "operator") { - if (a[i] != b[i]) + if (index + 1 < components.length) + { + currentComponent.leftNode = index+1; + index = postfixToTree(components, index+1); + } + + if (currentComponent.type == "operator" && index + 1 < components.length) { - return false; + currentComponent.rightNode = index+1; + index = postfixToTree(components, index+1); } } - return true; + return index; +} + +function strToTree(str) +{ + comp = expressionToComponentList(str); + let pf = componentListToPostfix(comp); + for (component of pf) + { + console.log(component.content, " "); + } + postfixToTree(pf.reverse()); + console.log("Tree: ", pf); } + +function cleanExpression(expression) +{ + let exp = expression; + // Remove all line breaks + exp = exp.replaceAll('\n', ''); + // Remove all whitespace + exp = exp.replaceAll(' ', ''); + return exp; +} + + From 79eef92d345e2c90e13dc32111632cb0d35b23ca Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sat, 6 Dec 2025 08:41:24 +0000 Subject: [PATCH 02/14] index/script.js --- pages/index/.script.js.swp | Bin 0 -> 24576 bytes pages/index/script.js | 45 ++++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 21 deletions(-) create mode 100644 pages/index/.script.js.swp diff --git a/pages/index/.script.js.swp b/pages/index/.script.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..38fba7f10a83d8715366fe1f7fcae48cd0cf699c GIT binary patch literal 24576 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 literal 0 HcmV?d00001 diff --git a/pages/index/script.js b/pages/index/script.js index b035654..ea6b6b0 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -364,34 +364,37 @@ function componentListToPostfix(list) } // Takes a list of components in postfix, and converts into tree -function postfixToTree(components, index=0) +// INPUTS: list of componets in the tree +// RETURNS: list Tree +function postfixToTree(components, index, depth) { - // If at end of components, return - if (index >= components.length) - return index; - + // Ironically, this works better with prefix, so convert + if (depth == 0) + components = components.reverse(); let currentComponent = components[index]; - // If number/constant, return - if (currentComponent.type == "number" || components[index].type == "constant") - return index; - // If function, add next component to left node - if (currentComponent.type == "function" || currentComponent.type == "operator") + switch(currentComponent.type) { - if (index + 1 < components.length) - { + case "function": + case "operator": { currentComponent.leftNode = index+1; - index = postfixToTree(components, index+1); - } + index = postfixToTree(components, index+1, depth+1); - if (currentComponent.type == "operator" && index + 1 < components.length) - { - currentComponent.rightNode = index+1; - index = postfixToTree(components, index+1); + if (currentComponent.type == "operator") + { + currentComponent.rightNode = index+1; + index = postfixToTree(components, index+1, depth+1); + } } + case "number": + case "constant": + break; } - return index; + if (depth > 0) + return index; + else + return components; } function strToTree(str) @@ -402,8 +405,8 @@ function strToTree(str) { console.log(component.content, " "); } - postfixToTree(pf.reverse()); - console.log("Tree: ", pf); + let tree = postfixToTree(pf.reverse()); + console.log("Tree: ", tree); } function cleanExpression(expression) From 0bada9f9e226e7f3ad922bd0808501ca8889cca7 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sat, 6 Dec 2025 08:56:20 +0000 Subject: [PATCH 03/14] Added parent node data to expression tree nodes All nodes now have a property that points to their parent. This enables graph normalisation (sorting). --- pages/index/script.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index ea6b6b0..e3e72b9 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -269,7 +269,8 @@ function expressionToComponentList(expression) content: content, type: type, leftNode: -1, - rightNode: -1 + rightNode: -1, + parent: -1 }; if (type == "operator" || type == "function") @@ -298,6 +299,7 @@ function expressionToComponentList(expression) precedence: 1, leftNode: -1, rightNode: -1, + parent: -1, commutative: true }); } @@ -366,24 +368,28 @@ function componentListToPostfix(list) // Takes a list of components in postfix, and converts into tree // INPUTS: list of componets in the tree // RETURNS: list Tree -function postfixToTree(components, index, depth) +function postfixToTree(components, index=0, parentIndex=-1, depth=0) { // Ironically, this works better with prefix, so convert if (depth == 0) components = components.reverse(); + let currentComponent = components[index]; + currentComponent.parent = parentIndex; switch(currentComponent.type) { case "function": case "operator": { + let componentIndex = index; + currentComponent.leftNode = index+1; - index = postfixToTree(components, index+1, depth+1); + index = postfixToTree(components, index+1, componentIndex, depth+1); if (currentComponent.type == "operator") { currentComponent.rightNode = index+1; - index = postfixToTree(components, index+1, depth+1); + index = postfixToTree(components, index+1, componentIndex, depth+1); } } case "number": From da409d3aea4addab084ec86afbaad38e7abd6aa4 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sat, 6 Dec 2025 23:31:45 +0000 Subject: [PATCH 04/14] Implemented graph normaliser. 1. Graph normaliser takes in a binary tree, and converts it into a standard form. It does this by ensuring the content on the left node on commutative operators is lower than the right node. 2. Requires pass to convert -1 patterns into subtractions (e.g: (-1 * 10) + 5 -> 5 - 10. This prevents potential comparison errors which can occur due to this discrepancy. --- pages/index/.script.js.swp | Bin 24576 -> 0 bytes pages/index/script.js | 108 +++++++++++++++++++++++++++++++++---- 2 files changed, 98 insertions(+), 10 deletions(-) delete mode 100644 pages/index/.script.js.swp diff --git a/pages/index/.script.js.swp b/pages/index/.script.js.swp deleted file mode 100644 index 38fba7f10a83d8715366fe1f7fcae48cd0cf699c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 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 diff --git a/pages/index/script.js b/pages/index/script.js index e3e72b9..7eda78f 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -1,6 +1,6 @@ // TODO: Eventually, put this in a separate file let RAW_SOLUTION = "1/2 x^2 sin(2x) + 1/2 x cos(2x) - 1/4 sin(2x) + c"; -let SOLUTION = expressionToDict(RAW_SOLUTION); +//let SOLUTION = expressionToDict(RAW_SOLUTION); let answerText = document.getElementById("answerText"); // Answer that appears on win modal answerText.innerText = `\\(${RAW_SOLUTION}\\)`; @@ -270,7 +270,8 @@ function expressionToComponentList(expression) type: type, leftNode: -1, rightNode: -1, - parent: -1 + parent: -1, + depth: -1 }; if (type == "operator" || type == "function") @@ -300,6 +301,7 @@ function expressionToComponentList(expression) leftNode: -1, rightNode: -1, parent: -1, + depth: -1, commutative: true }); } @@ -376,6 +378,7 @@ function postfixToTree(components, index=0, parentIndex=-1, depth=0) let currentComponent = components[index]; currentComponent.parent = parentIndex; + currentComponent.depth = depth; switch(currentComponent.type) { @@ -403,16 +406,103 @@ function postfixToTree(components, index=0, parentIndex=-1, depth=0) return components; } +// As there are multiple ways to write the same maths expression, there are multiple graphs that map to equivalent expressions. To compare equality of 2 graphs, they must first both be normalised (essentially, sorting elements under commutative operators by their content) +// INPUTS: list tree +// RETURNS: list normalised tree +function normaliseTree(tree) +{ + // Create a dictionary of depth:node indices + let layers = {}; + let maxLayer = 0; + for (let i = 0; i < tree.length; i++) + { + // If layer isn't in dict yet, add it + if (layers[tree[i].depth.toString()] == null) + layers[tree[i].depth.toString()] = []; + // Add node to layer + layers[tree[i].depth.toString()].push(i); + maxLayer = (tree[i].depth > maxLayer) ? tree[i].depth : maxLayer; + } + // For each layer, + for (let i = maxLayer; i > 0; i--) + { + // Go up one layer - find commutative nodes with 2 kids + for (parentIndex of layers[i-1]) + { + let parentNode = tree[parentIndex]; + if (parentNode.type == "operator" && parentNode.commutative == true) + { + // If so, compare contents. If R < L, swap parent's child pointers around + let requiresSwap = evaluateIfSwapNeeded(tree, parentNode.leftNode, parentNode.rightNode); + if (requiresSwap) + { + let temp = parentNode.rightNode; + parentNode.rightNode = parentNode.leftNode; + parentNode.leftNode = temp; + } + } + } + } + return tree; + // FIXME: NEEDS TO ALSO CONVERT -1* under + nodes to - +} + +// Compares 2 (sub)graphs, to determine if the right graph is "greater" than the left. +// If so, the root nodes of both graphs need swapping in their supergraph. +// Essentially performs a DFS simultaneously on both graphs, until the contents of the nodes being visited is different. +// In this case, compare the two. If right graph > left, return true. Else, return false. +// If no differences found, return false. +// INPUTS: int left index, int right index, (super)graph +function evaluateIfSwapNeeded(graph, leftRoot, rightRoot, left=leftRoot, right=rightRoot) +{ + // Compare left and right + if (graph[right].content > graph[left].content) + return true; + else if (graph[left].content > graph[right].content) + return false; + // If same, find next node in DFS of left and right + let nextLeftIndex = findNextInDFS(graph, leftRoot, left); + let nextRightIndex = findNextInDFS(graph, rightRoot, right); + + // If either = -1, then that graph had been fully traversed. Both graphs are the same, return false + if (nextLeftIndex == -1 || nextRightIndex == -1) + return false; + + // Else, recurse + return evaluateIfSwapNeeded(graph, leftRoot, rightRoot, nextLeftIndex, nextRightIndex); +} + +// Finds the index of the next node that should be observed in a DFS of binary tree +// INPUTS: binary tree, int index of root node, int index of the current node being considered +// RETURNS: int index of next node to be considered +function findNextInDFS(bTree, root, currentNodeIndex) +{ + // Find current node + let currentNode = bTree[currentNodeIndex]; + // If has left child, return its index + if (currentNode.leftNode != -1) + return currentNode.leftNode; + // If not, while current node is not root, + while (currentNode != 0) + { + // Set current node to parent + currentNode = currentNode.parent; + // If parent has right node, return it + if (currentNode.rightNode != -1) + return currentNode.rightNode; + } + // If this path is reached, DFS has ended. Return -1 + return -1; + if (currentNode.rightNode != -1) + return currentNode.rightNode; +} + function strToTree(str) { comp = expressionToComponentList(str); let pf = componentListToPostfix(comp); - for (component of pf) - { - console.log(component.content, " "); - } - let tree = postfixToTree(pf.reverse()); - console.log("Tree: ", tree); + let tree = postfixToTree(pf); + return tree; } function cleanExpression(expression) @@ -424,5 +514,3 @@ function cleanExpression(expression) exp = exp.replaceAll(' ', ''); return exp; } - - From 97fc18d7c59313e9c6eb7a491a4455419849817f Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Fri, 12 Dec 2025 21:45:24 +0000 Subject: [PATCH 05/14] Modified expressionToComponentList to lower -x+f(x) and f(x)-x to equivalent expressions Previously, -x+f(x) would lower to a component list of -1 * x + f(x). f(x)-x would lower to f(x) - x. As a result, this expression wouldn't lower to the same tree. Now, expressionToComponentList converts subtractions to additions of -1 * the number. e.g: a-b -> a + -1 * b. --- pages/index/script.js | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index 7eda78f..330fda7 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -153,22 +153,31 @@ function expressionToComponentList(expression) let i = 0; while (i < exp.length) { - // If a negative number that at the start, or after an open bracket/ function definition, add the implicit -1* - // e.g: -5x -> -1 * 5 x - // e.g: 10*(-x) -> 10 * ( -1 * x ) - // e.g: sin -x -> sin -1 * x - if ( + if (exp[i] == '-') + { + + // If not at start of expression/bracket, add + as well + // e.g: 5-2 -> 5+ -1 * 2 + if ( - list.length == 0 - || list.length > 0 && - ( - list[list.length - 1].type == "open bracket" - || list[list.length - 1].type == "function" - ) + !(list.length == 0 || + list.length > 0 && + (list[list.length - 1].type == "open bracket" + || list[list.length - 1].type == "function")) ) - && exp[i] == '-' - ) - { + { + list.push({ + content: "+", + type: "operator", + precedence: 0, + commutative: true, + leftNode: -1, + rightNode: -1, + parent: -1, + depth: -1 + }); + + } content = "-1"; type = "number"; From 95edb289f65308f43556ff8f415f1373a3c01e70 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Fri, 12 Dec 2025 23:29:11 +0000 Subject: [PATCH 06/14] Added tree comparison function Function takes in 2 binary trees, and evaluates whether their contents are equivalent. --- pages/index/.script.js.swp | Bin 0 -> 40960 bytes pages/index/script.js | 43 +++++++++++++++++++++++++++++++------ 2 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 pages/index/.script.js.swp diff --git a/pages/index/.script.js.swp b/pages/index/.script.js.swp new file mode 100644 index 0000000000000000000000000000000000000000..01eeb3620a59d8888d19f5ca7849b2778b125d0b GIT binary patch literal 40960 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 literal 0 HcmV?d00001 diff --git a/pages/index/script.js b/pages/index/script.js index 330fda7..f53a3c2 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -354,8 +354,14 @@ function componentListToPostfix(list) // If operator or function, look at stack else if (component.type == "operator" || component.type == "function") { + // If function, push + // Functions never cause an operator to be popped. e.g: in 1 * 2 + 3, the + causes the * to be popped. + // In 1 * sin(3), the sin doesn't cause the * to be popped. + if (component.type == "function") + operatorStack.push(component); + // If higher precedence than top of stack, push - if (operatorStack.length == 0 || component.precedence > operatorStack[operatorStack.length - 1].precedence) + else if (operatorStack.length == 0 || component.precedence > operatorStack[operatorStack.length - 1].precedence) { operatorStack.push(component); } @@ -456,6 +462,30 @@ function normaliseTree(tree) // FIXME: NEEDS TO ALSO CONVERT -1* under + nodes to - } +// Compares 2 binary trees, and returns true if their contents are equal. +// INPUTS: 2 binary trees - b1, b2 +// RETURNS: bool - are they equal? +function evaluateIfBTreesEqual(b1, b2) +{ + if (b1.length != b2.length) + return false; + + // Loop over trees until fully DFS'd + // If at any point, corresponding nodes don't match, trees are not equal + let b1CurrentNode = 0; + let b2CurrentNode = 0; + while (b1CurrentNode != -1) + { + if (b1[b1CurrentNode].content != b2[b2CurrentNode].content) + return false; + b1CurrentNode = findNextInDFS(b1, 0, b1CurrentNode); + b2CurrentNode = findNextInDFS(b2, 0, b2CurrentNode); + } + // If all equal, graphs are equal! + return true; + +} + // Compares 2 (sub)graphs, to determine if the right graph is "greater" than the left. // If so, the root nodes of both graphs need swapping in their supergraph. // Essentially performs a DFS simultaneously on both graphs, until the contents of the nodes being visited is different. @@ -492,18 +522,19 @@ function findNextInDFS(bTree, root, currentNodeIndex) if (currentNode.leftNode != -1) return currentNode.leftNode; // If not, while current node is not root, - while (currentNode != 0) + while (currentNodeIndex != root) { // Set current node to parent - currentNode = currentNode.parent; + let previousNodeIndex = currentNodeIndex; + currentNodeIndex = currentNode.parent; + currentNode = bTree[currentNodeIndex]; + // If parent has right node, return it - if (currentNode.rightNode != -1) + if (currentNode.rightNode != -1 && currentNode.rightNode != previousNodeIndex) return currentNode.rightNode; } // If this path is reached, DFS has ended. Return -1 return -1; - if (currentNode.rightNode != -1) - return currentNode.rightNode; } function strToTree(str) From 4aefa6a45feb5810d878f5e025201d8253c32b16 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Fri, 12 Dec 2025 23:43:53 +0000 Subject: [PATCH 07/14] Began adding bTree comparison into website flow --- pages/index/.script.js.swp | Bin 40960 -> 40960 bytes pages/index/script.js | 45 ++++++++++++++++++------------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/pages/index/.script.js.swp b/pages/index/.script.js.swp index 01eeb3620a59d8888d19f5ca7849b2778b125d0b..6476888681ac55ba7e401f550156827dcdd2110b 100644 GIT binary patch delta 897 zcmaLUOK1~87zgk#v5DO#x(RBLsF2n?b`iU+hDxjnCfGwHHKI|EbyK%A5VOs0))q9R zh@b_Z?8zeZ0TonINOSQ}uwe1(MGMk{SPAw}y;v_6!T+WN)PMuOnQvzPv(K?b^Vp)f zHPv}B*5?j(bP<|^sQmOqdiHU-^(Si@syH3r*?>`VIJn71S6n9 z6v7Y!A9%qG1XYG;6{cVk#vu$Y_|i=D5}v^fOv8Q9AP0lc(_AF7ph#~gibDjt!3OWG zL_ydOPO!p?g=iKSSZ^Zw1k3OOH2AQMXaUMlf(f_@m!Tgbup1n(j<3EJLRW>)unKQr z31(ml`hWu;9Dsdb{H_fX(WdFq^fCQco2!Ki#3&N_LS?hfWb#PY#Ed59rPyFJsYsIA zo72P$Cyfp(lB%ZVtg)!?Iyh+;gaJ7tYf4le4d@B~L$e_0anEU1%&H@jvD#r=Nz$Kt zf`4X@*0Tj~V|K;+SN2FfJLhZ6+WkS}mc9N7WBBNAOoX_Tdj+A;LkuGbOnVAX(|+9D zS)1w)xWmS|1+L2&SbmT0@K_ml$bEflpXIxoYVR831AK1(2)vdTX&)BV5gQdH_K8}MCz)- z-n-pIArk!&WI+*KNFVPZ*^lTc-Bw(^Iqdtj(mSHqm2}Pn*LOEZpL_H6S;~ovLzx5cm;)< z=z$L_#Uk^#f$IpN4DX6W?%*_Z`0=e!B!e^t5kzyLj7m9^F9jm+@dS^M#t4Scf*_i) z3p-(f8Ov6Y&zQkk#1KX?mMtPraSdY_!X@;e9U(MWMn!B)7B`4A!G*0T!AG;mJ(MDo z&xh~<4{;Goc_Pp87!NRv7R=;|OyUa05k~}m)WMD)IU-;2x}M7dUSb|Ea2L~Pvy~5TJ%gr?{#Z~ef?S@p~riJdNipHRNPWa6)833 z3T<919Xj6?8|X?BQg2-WWvfi7`<3ei@~Q%=p(>>otJVqBRR>hOI;9HT>jX}^8_P!a ziPdU#cE<)Z=UzuG59*nFw_0}l)OL?EH>M@kq-&Ep?s2HqkXc>!>?vCNNk_fPwp9d- zR}gfp^)H96r|B+R^^MZAY3S MC;ryc6W-yz-_?W7VgLXD diff --git a/pages/index/script.js b/pages/index/script.js index f53a3c2..0161f44 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -1,6 +1,6 @@ // TODO: Eventually, put this in a separate file let RAW_SOLUTION = "1/2 x^2 sin(2x) + 1/2 x cos(2x) - 1/4 sin(2x) + c"; -//let SOLUTION = expressionToDict(RAW_SOLUTION); +let SOLUTION = normaliseTree(strToTree(RAW_SOLUTION)); let answerText = document.getElementById("answerText"); // Answer that appears on win modal answerText.innerText = `\\(${RAW_SOLUTION}\\)`; @@ -42,42 +42,41 @@ function handleAnswerInputChange() { if (event.key == "Enter") { - let expressionDict = expressionToDict(answerBox.value); - let expressionEvaluation = evaluateExpression(expressionDict); + let expressionTree = normaliseTree(strToTree(answerBox.value)); // Update win modal - let answerCorrectness = evaluateCorrectness(expressionDict, SOLUTION); - let answerCorrectnessColour = { - "red": 255 * (100 - answerCorrectness) / 100, - "green": 255 * (answerCorrectness) / 100, - "blue": 0 - }; + //let answerCorrectness = evaluateCorrectness(expressionTree, SOLUTION); + //let answerCorrectnessColour = { + // "red": 255 * (100 - answerCorrectness) / 100, + // "green": 255 * (answerCorrectness) / 100, + // "blue": 0 + //}; let responseBox; switch (responseCount) { case 0: - response1.innerHTML = expressionEvaluation; - colourBox1.innerText = answerCorrectness + '%'; - colourBox1.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + response1.innerHTML = answerBox.value; + //colourBox1.innerText = answerCorrectness + '%'; + //colourBox1.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response1; break; case 1: - response2.innerHTML = expressionEvaluation; - colourBox2.innerText = answerCorrectness + '%'; - colourBox2.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + response2.innerHTML = answerBox.value; + //colourBox2.innerText = answerCorrectness + '%'; + //colourBox2.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response2; break; case 2: - response3.innerHTML = expressionEvaluation; - colourBox3.innerText = answerCorrectness + '%'; - colourBox3.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + response3.innerHTML = answerBox.value; + //colourBox3.innerText = answerCorrectness + '%'; + //colourBox3.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response3; break; case 3: - response4.innerHTML = expressionEvaluation; - colourBox4.innerText = answerCorrectness + '%'; - colourBox4.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + response4.innerHTML = answerBox.value; + //colourBox4.innerText = answerCorrectness + '%'; + //colourBox4.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response4; break; } @@ -86,7 +85,7 @@ function handleAnswerInputChange() MathJax.typeset([responseBox]); // Handle win (show pop-up) - if (checkWin(expressionDict)) + if (evaluateIfBTreesEqual(expressionTree, SOLUTION)) { winModal.style.display = "flex"; } @@ -143,7 +142,7 @@ function evaluateCorrectness(expressionDict, solutionDict) // Expression to component list function expressionToComponentList(expression) { - let Constants = ['e', "pi"]; + let Constants = ['e', "pi", 'c']; let exp = cleanExpression(expression); let list = []; let content = ""; From d98a80c6c33a3a8841cd80d074d6f3fa2b7047ce Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sat, 13 Dec 2025 23:09:58 +0000 Subject: [PATCH 08/14] Added and implemented Tree to MathJax expression function Function converts expression trees into MathJax expressions, which can be shown to the user. --- pages/index/script.js | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index 0161f44..4630e42 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -53,28 +53,29 @@ function handleAnswerInputChange() //}; let responseBox; + let responseMathJax = `\\(${treeToMathJax(expressionTree)}\\)`; switch (responseCount) { case 0: - response1.innerHTML = answerBox.value; + response1.innerHTML = responseMathJax; //colourBox1.innerText = answerCorrectness + '%'; //colourBox1.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response1; break; case 1: - response2.innerHTML = answerBox.value; + response2.innerHTML = responseMathJax; //colourBox2.innerText = answerCorrectness + '%'; //colourBox2.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response2; break; case 2: - response3.innerHTML = answerBox.value; + response3.innerHTML = responseMathJax; //colourBox3.innerText = answerCorrectness + '%'; //colourBox3.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response3; break; case 3: - response4.innerHTML = answerBox.value; + response4.innerHTML = responseMathJax; //colourBox4.innerText = answerCorrectness + '%'; //colourBox4.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response4; @@ -82,7 +83,7 @@ function handleAnswerInputChange() } responseCount++; // Render new mathjax - MathJax.typeset([responseBox]); + MathJax.typeset(); // Handle win (show pop-up) if (evaluateIfBTreesEqual(expressionTree, SOLUTION)) @@ -536,6 +537,31 @@ function findNextInDFS(bTree, root, currentNodeIndex) return -1; } +// Converts tree into a string which can.be interpreted by MathJax +// INPUTS: tree Expression bTree +// RETURNS: str that can be interpreted by MathJax +function treeToMathJax(tree, currentNodeIndex=0) +{ + let currentNode = tree[currentNodeIndex]; + switch (currentNode.type) + { + case "operator": + { + switch (currentNode.content) + { + case '/': + return `{${treeToMathJax(tree, currentNode.rightNode)}}\\over{${treeToMathJax(tree, currentNode.leftNode)}}`; + default: + return `{${treeToMathJax(tree, currentNode.rightNode)}}${currentNode.content}{${treeToMathJax(tree, currentNode.leftNode)}}`; + } + } + case "function": + return `\\${currentNode.content}{${treeToMathJax(tree, currentNode.leftNode)}}`; + default: + return currentNode.content; + } +} + function strToTree(str) { comp = expressionToComponentList(str); From 68bec6086eb4e55f884e8d611b371dd625fc2d86 Mon Sep 17 00:00:00 2001 From: Joshua Rodriguez Date: Sat, 13 Dec 2025 23:45:59 +0000 Subject: [PATCH 09/14] Corrected typo in comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pages/index/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/index/script.js b/pages/index/script.js index 4630e42..ef6118e 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -383,7 +383,7 @@ function componentListToPostfix(list) } // Takes a list of components in postfix, and converts into tree -// INPUTS: list of componets in the tree +// INPUTS: list of components in the tree // RETURNS: list Tree function postfixToTree(components, index=0, parentIndex=-1, depth=0) { From 786559d5f57531f4eb7d0b5b580ccd76763bfbdb Mon Sep 17 00:00:00 2001 From: Joshua Rodriguez Date: Sat, 13 Dec 2025 23:46:41 +0000 Subject: [PATCH 10/14] Corrected typo in comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pages/index/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/index/script.js b/pages/index/script.js index ef6118e..cc768e0 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -537,7 +537,7 @@ function findNextInDFS(bTree, root, currentNodeIndex) return -1; } -// Converts tree into a string which can.be interpreted by MathJax +// Converts tree into a string which can be interpreted by MathJax // INPUTS: tree Expression bTree // RETURNS: str that can be interpreted by MathJax function treeToMathJax(tree, currentNodeIndex=0) From 4485263f6178953076554d012143af30df70c510 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sat, 13 Dec 2025 23:09:58 +0000 Subject: [PATCH 11/14] Modified treeToMathJax to add brackets for complex terms "Complex" in this case means "The operator's children contain one or more other operators". This means the MathJax expression will need to surround that part of the expression with brackets. --- pages/index/.script.js.swp | Bin 40960 -> 0 bytes pages/index/script.js | 40 +++++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) delete mode 100644 pages/index/.script.js.swp diff --git a/pages/index/.script.js.swp b/pages/index/.script.js.swp deleted file mode 100644 index 6476888681ac55ba7e401f550156827dcdd2110b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40960 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 Date: Sat, 13 Dec 2025 23:56:51 +0000 Subject: [PATCH 12/14] Removed commented / un-used code --- pages/index/script.js | 50 ------------------------------------------- 1 file changed, 50 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index fac2fce..07ba144 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -58,26 +58,18 @@ function handleAnswerInputChange() { case 0: response1.innerHTML = responseMathJax; - //colourBox1.innerText = answerCorrectness + '%'; - //colourBox1.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response1; break; case 1: response2.innerHTML = responseMathJax; - //colourBox2.innerText = answerCorrectness + '%'; - //colourBox2.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response2; break; case 2: response3.innerHTML = responseMathJax; - //colourBox3.innerText = answerCorrectness + '%'; - //colourBox3.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response3; break; case 3: response4.innerHTML = responseMathJax; - //colourBox4.innerText = answerCorrectness + '%'; - //colourBox4.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; responseBox = response4; break; } @@ -97,48 +89,6 @@ function handleCloseModal() { winModal.style.display = "none"; } -// TODO: Checks if input expression is a valid maths expression in the first place -function checkValidInputExpression() -{ - return; -} - -// Takes in dict of expression, and constructs an evaluation of each term in that expression -// INPUTS: dict expressionDict -// RETURNS: str html of answer evaluation (to go in a response block) -// Each term in expression is coloured: -// - Red: Term doesn't exist in answer -// - Yellow: Term does exist, but coefficient is incorrect -// - Green: Term and coefficient correct -// A number is also appended on end, which corresponds to number of missing terms in expression -function evaluateExpression(expressionDict) -{ -} - -// Ranks term as red, yellow or green -// - Red: Term doesn't exist in answer -// - Yellow: Term does exist, but coefficient is incorrect -// - Green: Term and coefficient correct -// INPUTS: str term, int coeff, dict solution -// RETURNS: str red/yellow/green -function evaluateTerm(term, coeff, solution) -{ -} - -// Calculates number of terms in solution that are missing from expression -// INPUTS: dict expressionDict, dict solutionDict -// RETURNS: int number of missing terms -function calculateMissingTerms(expressionDict, solutionDict) -{ -} - -// Evaluates, as a %, how correct the input expression was. -// Used for the boxes on the win modal -// INPUTS: dict expressionDict, dict solutionDict -// RETURNS: int % correctness of expression -function evaluateCorrectness(expressionDict, solutionDict) -{ -} // Expression to component list function expressionToComponentList(expression) From db760216c71b08ba0308154239a3c8be5735c616 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Wed, 17 Dec 2025 22:20:42 +0000 Subject: [PATCH 13/14] Added colour to back of responses based on correctness of answer Formula for calculating correctness = 100(1 - e^(no. identical nodes /(no. nodes in solution + difference in no. nodes))). --- pages/index/script.js | 49 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index 07ba144..c4df2d4 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -45,12 +45,12 @@ function handleAnswerInputChange() let expressionTree = normaliseTree(strToTree(answerBox.value)); // Update win modal - //let answerCorrectness = evaluateCorrectness(expressionTree, SOLUTION); - //let answerCorrectnessColour = { - // "red": 255 * (100 - answerCorrectness) / 100, - // "green": 255 * (answerCorrectness) / 100, - // "blue": 0 - //}; + let answerCorrectness = evaluateCorrectness(expressionTree, SOLUTION); + let answerCorrectnessColour = { + "red": 255 * (100 - answerCorrectness) / 100, + "green": 255 * (answerCorrectness) / 100, + "blue": 0 + }; let responseBox; let responseMathJax = `\\(${treeToMathJax(expressionTree)}\\)`; @@ -59,18 +59,26 @@ function handleAnswerInputChange() case 0: response1.innerHTML = responseMathJax; responseBox = response1; + response1.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + colourBox1.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; break; case 1: response2.innerHTML = responseMathJax; responseBox = response2; + response2.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + colourBox2.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; break; case 2: response3.innerHTML = responseMathJax; responseBox = response3; + response3.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + colourBox3.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; break; case 3: response4.innerHTML = responseMathJax; responseBox = response4; + response4.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + colourBox4.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; break; } responseCount++; @@ -436,6 +444,35 @@ function evaluateIfBTreesEqual(b1, b2) } +// Takes in 2 expression trees, and compares how similar the 1st is to the 2nd +// FORMULA: (1 - e^(no. identical nodes / no. nodes in exp2 + difference in no. nodes between 2 trees)) * 100 +// INPUTS: 2 expression trees - b1, b2 +// RETURNS: int (0 - 100) correctness: 100 = correct, 0 = completely incorrect +function evaluateCorrectness(exp1, exp2) +{ + // If trees are equal, then return 100 + let equal = evaluateIfBTreesEqual(exp1, exp2); + if (equal) + return 100; + + let difference = Math.abs(exp1.length - exp2.length); + // Loop over trees until fully DFS'd + // If nodes are identical, add 1 to count + let identicalNodesCount = 0; + let exp1CurrentNode = 0; + let exp2CurrentNode = 0; + for (let i = 0; i < Math.min(exp1.length, exp2.length); i++) + { + if (exp1[exp1CurrentNode].content == exp2[exp2CurrentNode].content) + identicalNodesCount++; + + exp1CurrentNode = findNextInDFS(exp1, 0, exp1CurrentNode); + exp2CurrentNode = findNextInDFS(exp2, 0, exp2CurrentNode); + } + + return (1 - Math.exp(-1 * (identicalNodesCount / (exp2.length + difference)))) * 100; +} + // Compares 2 (sub)graphs, to determine if the right graph is "greater" than the left. // If so, the root nodes of both graphs need swapping in their supergraph. // Essentially performs a DFS simultaneously on both graphs, until the contents of the nodes being visited is different. From f75ecb3623ec372de484bb1a14beb2c53874a05d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sun, 21 Dec 2025 19:46:36 +0000 Subject: [PATCH 14/14] Move correctness -> rgb string into helper function (#12) Addresses question on PR #9 about helper function refactoring to reduce code duplication in response colour coding. ## Changes - Added `getColourString()` helper function to encapsulate RGB string formatting - Extracted repeated color string construction (8 occurrences) into single variable --- pages/index/script.js | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index c4df2d4..813d156 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -38,6 +38,18 @@ const policy = trustedTypes.createPolicy("my-policy", let responseCount = 0; +// Helper function to convert answer correctness to RGB colour string +function getColourString(correctness) +{ + let colour = { + "red": 255 * (100 - correctness) / 100, + "green": 255 * (correctness) / 100, + "blue": 0 + }; + + return `rgb(${colour["red"]}, ${colour["green"]}, ${colour["blue"]})`; +} + function handleAnswerInputChange() { if (event.key == "Enter") @@ -46,12 +58,7 @@ function handleAnswerInputChange() // Update win modal let answerCorrectness = evaluateCorrectness(expressionTree, SOLUTION); - let answerCorrectnessColour = { - "red": 255 * (100 - answerCorrectness) / 100, - "green": 255 * (answerCorrectness) / 100, - "blue": 0 - }; - + let correctnessColour = getColourString(answerCorrectness); let responseBox; let responseMathJax = `\\(${treeToMathJax(expressionTree)}\\)`; switch (responseCount) @@ -59,26 +66,26 @@ function handleAnswerInputChange() case 0: response1.innerHTML = responseMathJax; responseBox = response1; - response1.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; - colourBox1.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + response1.style.backgroundColor = correctnessColour; + colourBox1.style.backgroundColor = correctnessColour; break; case 1: response2.innerHTML = responseMathJax; responseBox = response2; - response2.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; - colourBox2.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + response2.style.backgroundColor = correctnessColour; + colourBox2.style.backgroundColor = correctnessColour; break; case 2: response3.innerHTML = responseMathJax; responseBox = response3; - response3.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; - colourBox3.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + response3.style.backgroundColor = correctnessColour; + colourBox3.style.backgroundColor = correctnessColour; break; case 3: response4.innerHTML = responseMathJax; responseBox = response4; - response4.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; - colourBox4.style.backgroundColor = `rgb(${answerCorrectnessColour["red"]}, ${answerCorrectnessColour["green"]}, ${answerCorrectnessColour["blue"]})`; + response4.style.backgroundColor = correctnessColour; + colourBox4.style.backgroundColor = correctnessColour; break; } responseCount++;