From d21c6040cdc32ff360d7a9738f23665788fdc410 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Thu, 4 Dec 2025 23:02:44 +0000 Subject: [PATCH 01/28] 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 c5c894699ca31a2dd1e6073dffd50b060d575a59 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sat, 6 Dec 2025 08:41:24 +0000 Subject: [PATCH 02/28] 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 d2fc0027620b4e8a778b15c6c7b8c4f4223c8c21 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sat, 6 Dec 2025 08:56:20 +0000 Subject: [PATCH 03/28] 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 44271f45c786321a5fff5e82ad6d4acdf104594d Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sat, 6 Dec 2025 23:31:45 +0000 Subject: [PATCH 04/28] 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 25f4190166fc897804bde543274107b4d54c8913 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Fri, 12 Dec 2025 21:45:24 +0000 Subject: [PATCH 05/28] 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 844069a57360591cdf903cd07d38f47951c9716a Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Fri, 12 Dec 2025 23:29:11 +0000 Subject: [PATCH 06/28] 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 85335846ccae6e82fe769c191c184ff4693ea897 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Fri, 12 Dec 2025 23:43:53 +0000 Subject: [PATCH 07/28] 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 69c74c2967d57003a4e2c5474ed601373203cd85 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sat, 13 Dec 2025 23:09:58 +0000 Subject: [PATCH 08/28] 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 8c31ec55b859bd239dba1c81fa235ab81a4b9b71 Mon Sep 17 00:00:00 2001 From: Joshua Rodriguez Date: Sat, 13 Dec 2025 23:45:59 +0000 Subject: [PATCH 09/28] 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 b6d5e017e80de5e16e70beded4ff532148f9cbf9 Mon Sep 17 00:00:00 2001 From: Joshua Rodriguez Date: Sat, 13 Dec 2025 23:46:41 +0000 Subject: [PATCH 10/28] 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 8ef7c9c7a57226cce6ebf4149e2f029346758b2f Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sat, 13 Dec 2025 23:09:58 +0000 Subject: [PATCH 11/28] 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/28] 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 8f83fdf90bc3460b5d0665ae814e26857cd389df Mon Sep 17 00:00:00 2001 From: Joshua Rodriguez Date: Sat, 13 Dec 2025 23:59:16 +0000 Subject: [PATCH 13/28] Indented if block with tab instead of space Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pages/index/script.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index 07ba144..2b1f085 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -233,11 +233,12 @@ function expressionToComponentList(expression) depth: -1 }; - if (type == "operator" || type == "function") + if (type == "operator" || type == "function") { newComponent.precedence = precedence; - if (type == "operator") + if (type == "operator") { newComponent.commutative = commutative; - + } + } list.push(newComponent); // Check for implicit * signs From b9c96e0049213683af85332c99a3d6af99d5fdd5 Mon Sep 17 00:00:00 2001 From: Joshua Rodriguez Date: Sun, 14 Dec 2025 00:01:28 +0000 Subject: [PATCH 14/28] Added missing declaration for local variable 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 2b1f085..b60dcfb 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -547,7 +547,7 @@ function treeToMathJax(tree, currentNodeIndex=0) function strToTree(str) { - comp = expressionToComponentList(str); + let comp = expressionToComponentList(str); let pf = componentListToPostfix(comp); let tree = postfixToTree(pf); return tree; From 07a0c1d2a868ab474a4c09c271f9a7070f0e4c78 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Wed, 17 Dec 2025 22:24:43 +0000 Subject: [PATCH 15/28] Added missing break in switch case statement --- pages/index/script.js | 1 + 1 file changed, 1 insertion(+) diff --git a/pages/index/script.js b/pages/index/script.js index b60dcfb..e2b0fa7 100644 --- a/pages/index/script.js +++ b/pages/index/script.js @@ -360,6 +360,7 @@ function postfixToTree(components, index=0, parentIndex=-1, depth=0) currentComponent.rightNode = index+1; index = postfixToTree(components, index+1, componentIndex, depth+1); } + break; } case "number": case "constant": From 9dd3745ed99d3b6e32dc0c301e001cec77b435ec Mon Sep 17 00:00:00 2001 From: Joshua Rodriguez Date: Sun, 21 Dec 2025 20:11:30 +0000 Subject: [PATCH 16/28] Add colour to back of responses based on correctness of answer (#13) Formula for calculating correctness = 100(1 - e^(-(no. identical nodes /(no. nodes in solution + difference in no. nodes)))). Same as #9, just accidentally force pushed without adding these changes to my local branch. --- pages/index/script.js | 58 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/pages/index/script.js b/pages/index/script.js index e2b0fa7..de68fa7 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") @@ -45,13 +57,8 @@ 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 correctnessColour = getColourString(answerCorrectness); let responseBox; let responseMathJax = `\\(${treeToMathJax(expressionTree)}\\)`; switch (responseCount) @@ -59,18 +66,26 @@ function handleAnswerInputChange() case 0: response1.innerHTML = responseMathJax; responseBox = response1; + response1.style.backgroundColor = correctnessColour; + colourBox1.style.backgroundColor = correctnessColour; break; case 1: response2.innerHTML = responseMathJax; responseBox = response2; + response2.style.backgroundColor = correctnessColour; + colourBox2.style.backgroundColor = correctnessColour; break; case 2: response3.innerHTML = responseMathJax; responseBox = response3; + response3.style.backgroundColor = correctnessColour; + colourBox3.style.backgroundColor = correctnessColour; break; case 3: response4.innerHTML = responseMathJax; responseBox = response4; + response4.style.backgroundColor = correctnessColour; + colourBox4.style.backgroundColor = correctnessColour; break; } responseCount++; @@ -438,6 +453,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 b837beb1049eedb612de36bb67d62247fbca45d5 Mon Sep 17 00:00:00 2001 From: JoshdRod Date: Sun, 21 Dec 2025 20:05:22 +0000 Subject: [PATCH 17/28]