import ccm import re from ccm.lib.actr import * import tree import queue class TutorState: ''' Class to store the state that the tutor thinks the equation is in Tracks this by generating an equation tree from the tree.py file, and moving nodes accordingly ''' def __init__(self,equation): '''Initializes tree with an equation, and generates equation tree from this equation.''' self.equation = equation self.expressions = equation.split("=") self.tree = tree.generate_tree(equation) def move_values(self, left, constants): ''' If the user is moving either variables or constants, the tutor will call this function to rearrange nodes in the tree accordingly ''' moveNode = None newOp = None q = queue.Queue() if left: # if moving values from right to left node = self.tree.root.right q.enqueue(self.tree.root.left) else: # if moving values from left to right node = self.tree.root.left q.enqueue(self.tree.root.right) if constants: # if moving constants nodeType = tree.NumNode emptyNode = tree.VariableNode("0x") else: # if moving variables nodeType = tree.VariableNode emptyNode = tree.NumNode(0) if isinstance(node, tree.PlusNode) or isinstance(node, tree.MinusNode): # if plus or minus node if node.canEval() == False: # if can't evaluate children, need to move one to the other side newOp = node.getInverse() if isinstance(node.left, nodeType): moveNode = node.left node.left = emptyNode elif isinstance(node.right, nodeType): moveNode = node.right node.right = emptyNode moved = False # trackes if a node has been moved yet while not q.isEmpty() and not moved: currentNode = q.front() if currentNode.left != None: if isinstance(currentNode.left, nodeType): oldNum = currentNode.left currentNode.left = newOp currentNode.left.left = oldNum currentNode.left.right = moveNode moved = True q.enqueue(currentNode.left) if currentNode.right != None and not moved: if isinstance(currentNode.right, nodeType): oldNum = currentNode.right currentNode.right = newOp currentNode.right.left = oldNum currentNode.right.right = moveNode moved = True q.enqueue(currentNode.right) if isinstance(currentNode, nodeType) and not moved: # case where there is no operator and want to shift node over oldNum =currentNode if left: self.tree.root.left = newOp self.tree.root.left.left = oldNum self.tree.root.left.right = moveNode else: self.tree.root.right = newOp self.tree.root.right.left = oldNum self.tree.root.right.right = moveNode moved=True q.dequeue() if left: self.combine_like_terms(False) else: self.combine_like_terms(True) return self.tree.get_expression() def combine_like_terms(self, left): ''' Combines like terms on a designated side of the tree by simplifying like terms. ''' self.tree.get_expression() if left: self.tree.evaluate(self.tree.root, True) else: self.tree.evaluate(self.tree.root, False) return self.tree.get_expression() def compare_Equations(self, equation1, equation2): ''' Compares two equations to see if both sides are equivalent, regardless of their order Assumes equations have operators that do not require a specific order ''' equation1 = "".join(equation1.split()) equation2 = "".join(equation2.split()) eq1 = equation1.split('=') eq2 = equation2.split('=') eq1list = [] eq2list = [] for side in eq1: eq1list.append(sorted(re.split('[-|+|*|/]',side))) for side in eq2: eq2list.append(sorted(re.split('[-|+|*|/]',side))) #print(eq1list,eq2list) return eq1list==eq2list class UserState(ccm.Model): ''' Class for the user state to track where the user is in their solution ''' def __init__(self, equation): ''' Initializes the model itself, saves the equation, initial sides of the equation and initial count of variable types. ''' ccm.Model.__init__(self) self.equation = equation self.oldEquation = None self.sides = self.getLeftandRightVals() self.constantCount = self.getConstantCount() self.state = None def get_input(self): ''' Gets input from the user by showing them the current state and asking for the next one. Takes their input and saves all needed information to process it accordingly. ''' outputString = "Equation is currently: " + str(self.equation) + "\nWhat is your next step? " user_step = input(outputString) self.oldEquation = self.equation self.equation = user_step oldSides = self.sides self.sides = self.getLeftandRightVals() oldConstantCount = self.constantCount self.constantCount = self.getConstantCount() self.state = self.get_state(oldConstantCount) def getLeftandRightVals(self): ''' Splits equation to get left and right sides of equation ''' expressions = self.equation.split("=") try: leftVars = re.split("[+-]", expressions[0]) rightVars = re.split("[+-]", expressions[1]) except: print("INVALID INPUT, try again") self.equation=self.oldEquation self.get_input() return [leftVars, rightVars] def getConstantCount(self): ''' Counts number of variables of each type on each side of the equation ''' constantsOnLeft = 0 variablesOnLeft = 0 constantsOnRight = 0 variablesOnRight = 0 for value in self.sides[0]: if self.isConstant(value): constantsOnLeft += 1 elif 'x' in value: variablesOnLeft += 1 for value in self.sides[1]: if self.isConstant(value): constantsOnRight += 1 elif 'x' in value: variablesOnRight += 1 return [variablesOnLeft, constantsOnLeft, variablesOnRight, constantsOnRight] def isConstant(self, value): ''' Given a value, checks if it is a constant. ''' if not value.isdigit(): return False return True def get_state(self, oldConstantCount): ''' Based on number of variables and constants on each side of the equation the agent guesses which state the user is moving to ''' if oldConstantCount[0] != self.constantCount[0] and oldConstantCount[2] != self.constantCount[2]: # if not same number of variables as before for both sides then moved variables if oldConstantCount[0] < self.constantCount[0]: self.state = "move_variables 1" else: self.state = "move_variables 0" elif oldConstantCount[0] != self.constantCount[0] or oldConstantCount[2] != self.constantCount[2]: if oldConstantCount[0] > self.constantCount[0]: self.state = "add_variables 1" else: self.state = "add_variables 0" elif oldConstantCount[1] != self.constantCount[1] and oldConstantCount[3] != self.constantCount[3]: if oldConstantCount[1] < self.constantCount[1]: #moving constants to left self.state = "move_constants 1" else: self.state = "move_constants 0" elif oldConstantCount[1] != self.constantCount[1] or oldConstantCount[3] != self.constantCount[3]: if oldConstantCount[1] > self.constantCount[1]: self.state = "add_constants 1" else: self.state = "add_constants 0" elif ((self.constantCount[1] == 1 and self.constantCount[2] == 1) or (self.constantCount[0] == 1 and self.constantCount[3] == 1)) and (self.sides[0][0] == 'x' or self.sides[1][0] == 'x'): # case where simplifying to solve for x self.state = "solve_for_x" else: print("nothing triggered") self.state = "invalid_state nothing" return self.state class IntelligentTutor(ACTR): ''' RBES for the tutor itself. Tracks states ''' goal = Buffer() user = UserState("4x+7=5x+2") tutor = TutorState("4x + 7 = 5x + 2") def init(): user.get_input() print("USER STATE IS ", user.state) # need to get user input to translate to state goal.set(user.state) def moved_constants(goal="move_constants ?left"): #true if moving constants left tutorEq = tutor.move_values(bool(int(left)), True) print("user equation ",user.equation) goal.set("check_state " + tutorEq + " 0") def add_constants(goal= "add_constants ?left"): tutorEq = tutor.combine_like_terms(bool(int(left))) print("user equation ",user.equation) goal.set("check_state " + tutorEq + " 0") def moved_variables(goal="move_variables ?left"): tutorEq = tutor.move_values(bool(int(left)), False) print("user equation ",user.equation) goal.set("check_state " + tutorEq + " 0") def add_variables(goal = "add_variables ?left"): tutorEq = tutor.combine_like_terms(bool(int(left))) print("user equation", user.equation) goal.set("check_state " + tutorEq + " 0") def solve(goal = "solve_for_x"): tutorEq = tutor.tree.solve() goal.set("check_state " + tutorEq + " 1") def check_state(goal = "check_state ?tutorEq ?solved"): print("checking state") if tutor.compare_Equations(tutorEq, user.equation): if bool(int(solved)): print("You have solved the equation by isolating x.") goal.set("end_process") else: user.get_input() goal.set(user.state) else: goal.set("invalid_state " + tutorEq) def incorrect_state(goal = "invalid_state ?tutorEqn"): print("You have entered an invalid state. We anticipated the correct step to be: ", tutorEqn, "\nPlease continue solving the problem with the corrected equation.") user.equation = tutorEqn user.get_input() goal.set(user.state) def end_process(goal = "end_process"): goal.set("Simulation Complete!") self.stop() class EmptyEnvironment(ccm.Model): pass def main(): env_name = EmptyEnvironment() agent_name = IntelligentTutor() env_name.agent = agent_name ccm.log_everything(env_name) # log statement, uncomment if you want to log information env_name.run() main()