Skip to content

Latest commit

 

History

History
1015 lines (810 loc) · 34.4 KB

File metadata and controls

1015 lines (810 loc) · 34.4 KB

Binary Math Education System - Implementation Guide

Companion to: ARCHITECTURE.md Purpose: Detailed implementation templates and code examples Version: 1.0 Date: 2025-10-15


Quick Start Implementation Templates

This guide provides ready-to-use code templates for implementing the Binary Math Education System. Each section includes complete, working examples that follow the architecture specification.


1. Expression Parser Implementation

Complete Parser Class

from dataclasses import dataclass
from typing import Optional, List, Dict, Set
from enum import Enum

class TokenType(Enum):
    AND = "AND"
    OR = "OR"
    NOT = "NOT"
    XOR = "XOR"
    LPAREN = "("
    RPAREN = ")"
    VARIABLE = "VAR"
    EOF = "EOF"

@dataclass
class Token:
    type: TokenType
    value: str
    position: int

@dataclass
class ExpressionNode:
    operator: Optional[str] = None
    left: Optional['ExpressionNode'] = None
    right: Optional['ExpressionNode'] = None
    variable: Optional[str] = None

    def evaluate(self, values: Dict[str, bool]) -> bool:
        """Evaluate the expression tree with given variable values"""
        if self.variable is not None:
            return values[self.variable]

        if self.operator == "NOT":
            return not self.left.evaluate(values)
        elif self.operator == "AND":
            return self.left.evaluate(values) and self.right.evaluate(values)
        elif self.operator == "OR":
            return self.left.evaluate(values) or self.right.evaluate(values)
        elif self.operator == "XOR":
            return self.left.evaluate(values) != self.right.evaluate(values)

        raise ValueError(f"Unknown operator: {self.operator}")

    def to_string(self) -> str:
        """Convert expression tree back to string"""
        if self.variable is not None:
            return self.variable

        if self.operator == "NOT":
            return f"NOT {self.left.to_string()}"

        left_str = self.left.to_string()
        right_str = self.right.to_string()

        # Add parentheses if needed
        if self.operator in ["AND", "OR", "XOR"]:
            return f"({left_str} {self.operator} {right_str})"

        return f"{left_str} {self.operator} {right_str}"

    def complexity_score(self) -> int:
        """Calculate complexity score of expression"""
        if self.variable is not None:
            return 1

        left_score = self.left.complexity_score() if self.left else 0
        right_score = self.right.complexity_score() if self.right else 0

        # NOT adds 2, binary operators add 3
        operator_weight = 2 if self.operator == "NOT" else 3

        return operator_weight + left_score + right_score

class ExpressionParser:
    """Recursive descent parser for logic expressions"""

    def __init__(self):
        self.tokens: List[Token] = []
        self.current = 0

    def parse(self, expression: str) -> ExpressionNode:
        """Parse expression string into AST"""
        self.tokens = self.tokenize(expression)
        self.current = 0

        if not self.tokens:
            raise ValueError("Empty expression")

        result = self._parse_or_expr()

        if self.current < len(self.tokens) and self.tokens[self.current].type != TokenType.EOF:
            raise ValueError(f"Unexpected token: {self.tokens[self.current].value}")

        return result

    def tokenize(self, expression: str) -> List[Token]:
        """Convert expression string to tokens"""
        tokens = []
        i = 0
        expression = expression.strip()

        while i < len(expression):
            # Skip whitespace
            if expression[i].isspace():
                i += 1
                continue

            # Check for operators and parentheses
            if expression[i:i+3] == "AND":
                tokens.append(Token(TokenType.AND, "AND", i))
                i += 3
            elif expression[i:i+2] == "OR":
                tokens.append(Token(TokenType.OR, "OR", i))
                i += 2
            elif expression[i:i+3] == "NOT":
                tokens.append(Token(TokenType.NOT, "NOT", i))
                i += 3
            elif expression[i:i+3] == "XOR":
                tokens.append(Token(TokenType.XOR, "XOR", i))
                i += 3
            elif expression[i] == "(":
                tokens.append(Token(TokenType.LPAREN, "(", i))
                i += 1
            elif expression[i] == ")":
                tokens.append(Token(TokenType.RPAREN, ")", i))
                i += 1
            elif expression[i].isalpha():
                # Variable name (single uppercase letter)
                tokens.append(Token(TokenType.VARIABLE, expression[i], i))
                i += 1
            else:
                raise ValueError(f"Unexpected character at position {i}: {expression[i]}")

        tokens.append(Token(TokenType.EOF, "", len(expression)))
        return tokens

    def extract_variables(self, expression: str) -> Set[str]:
        """Extract all variable names from expression"""
        tokens = self.tokenize(expression)
        return {t.value for t in tokens if t.type == TokenType.VARIABLE}

    def validate_syntax(self, expression: str) -> tuple[bool, str]:
        """Validate expression syntax, return (is_valid, error_message)"""
        try:
            self.parse(expression)
            return True, ""
        except Exception as e:
            return False, str(e)

    # Recursive descent parsing methods
    def _parse_or_expr(self) -> ExpressionNode:
        """Parse OR expressions (lowest precedence)"""
        left = self._parse_xor_expr()

        while self._match(TokenType.OR):
            operator = self._previous().value
            right = self._parse_xor_expr()
            left = ExpressionNode(operator=operator, left=left, right=right)

        return left

    def _parse_xor_expr(self) -> ExpressionNode:
        """Parse XOR expressions"""
        left = self._parse_and_expr()

        while self._match(TokenType.XOR):
            operator = self._previous().value
            right = self._parse_and_expr()
            left = ExpressionNode(operator=operator, left=left, right=right)

        return left

    def _parse_and_expr(self) -> ExpressionNode:
        """Parse AND expressions"""
        left = self._parse_not_expr()

        while self._match(TokenType.AND):
            operator = self._previous().value
            right = self._parse_not_expr()
            left = ExpressionNode(operator=operator, left=left, right=right)

        return left

    def _parse_not_expr(self) -> ExpressionNode:
        """Parse NOT expressions (highest precedence for unary)"""
        if self._match(TokenType.NOT):
            operator = self._previous().value
            expr = self._parse_not_expr()  # Right associative
            return ExpressionNode(operator=operator, left=expr)

        return self._parse_primary()

    def _parse_primary(self) -> ExpressionNode:
        """Parse primary expressions (variables and parenthesized expressions)"""
        if self._match(TokenType.VARIABLE):
            return ExpressionNode(variable=self._previous().value)

        if self._match(TokenType.LPAREN):
            expr = self._parse_or_expr()
            if not self._match(TokenType.RPAREN):
                raise ValueError("Expected ')'")
            return expr

        raise ValueError(f"Unexpected token: {self._peek().value}")

    # Helper methods
    def _match(self, *types: TokenType) -> bool:
        """Check if current token matches any of the given types"""
        for token_type in types:
            if self._check(token_type):
                self._advance()
                return True
        return False

    def _check(self, token_type: TokenType) -> bool:
        """Check if current token is of given type"""
        if self._is_at_end():
            return False
        return self._peek().type == token_type

    def _advance(self) -> Token:
        """Move to next token"""
        if not self._is_at_end():
            self.current += 1
        return self._previous()

    def _is_at_end(self) -> bool:
        """Check if at end of tokens"""
        return self.current >= len(self.tokens) or self._peek().type == TokenType.EOF

    def _peek(self) -> Token:
        """Get current token without advancing"""
        if self.current < len(self.tokens):
            return self.tokens[self.current]
        return self.tokens[-1]  # Return EOF token

    def _previous(self) -> Token:
        """Get previous token"""
        return self.tokens[self.current - 1]

2. Truth Table Generator Implementation

from itertools import product
from typing import List, Tuple

@dataclass
class TruthTableRow:
    inputs: Dict[str, bool]
    output: bool

    def to_string(self, variables: List[str]) -> str:
        """Format row as string"""
        input_str = ", ".join(f"{v}={int(self.inputs[v])}" for v in variables)
        return f"{input_str} => {int(self.output)}"

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for JSON serialization"""
        return {
            "inputs": {k: int(v) for k, v in self.inputs.items()},
            "output": int(self.output)
        }

@dataclass
class TruthTable:
    expression: str
    variables: List[str]
    rows: List[TruthTableRow]

    def to_text_table(self) -> str:
        """Generate formatted text table"""
        if not self.rows:
            return "Empty truth table"

        # Header
        header = " | ".join(self.variables) + " | Result"
        separator = "-" * len(header)

        # Rows
        row_strings = []
        for row in self.rows:
            values = [str(int(row.inputs[v])) for v in self.variables]
            values.append(str(int(row.output)))
            row_strings.append(" | ".join(f"{v:^{len(self.variables[i]) if i < len(self.variables) else 6}}"
                                         for i, v in enumerate(values)))

        return "\n".join([header, separator] + row_strings)

    def to_structured_dict(self) -> Dict:
        """Convert to structured dictionary"""
        return {
            "expression": self.expression,
            "variables": self.variables,
            "rows": [row.to_dict() for row in self.rows],
            "num_rows": len(self.rows)
        }

    def get_minterms(self) -> List[str]:
        """Get minterms (rows where output is True)"""
        minterms = []
        for i, row in enumerate(self.rows):
            if row.output:
                minterms.append(f"m{i}")
        return minterms

    def get_maxterms(self) -> List[str]:
        """Get maxterms (rows where output is False)"""
        maxterms = []
        for i, row in enumerate(self.rows):
            if not row.output:
                maxterms.append(f"M{i}")
        return maxterms

class TruthTableGenerator:
    """Generate truth tables from logic expressions"""

    def __init__(self):
        self.parser = ExpressionParser()

    def generate(self, expression: str) -> TruthTable:
        """Generate complete truth table for expression"""
        # Parse expression
        expr_tree = self.parser.parse(expression)

        # Extract variables and sort alphabetically
        variables = sorted(self.parser.extract_variables(expression))

        if not variables:
            raise ValueError("Expression must contain at least one variable")

        # Generate all possible input combinations
        rows = []
        for values in product([False, True], repeat=len(variables)):
            input_dict = dict(zip(variables, values))
            output = expr_tree.evaluate(input_dict)
            rows.append(TruthTableRow(inputs=input_dict, output=output))

        return TruthTable(
            expression=expression,
            variables=variables,
            rows=rows
        )

@dataclass
class ValidationResult:
    is_correct: bool
    errors: List[str]
    suggestions: List[str]
    correct_rows: int
    total_rows: int

    def get_score(self) -> float:
        """Calculate percentage score"""
        if self.total_rows == 0:
            return 0.0
        return (self.correct_rows / self.total_rows) * 100

    def get_detailed_feedback(self) -> str:
        """Generate detailed feedback message"""
        feedback = [f"Score: {self.get_score():.1f}% ({self.correct_rows}/{self.total_rows} correct)"]

        if self.errors:
            feedback.append("\nErrors:")
            feedback.extend(f"  - {error}" for error in self.errors)

        if self.suggestions:
            feedback.append("\nSuggestions:")
            feedback.extend(f"  - {suggestion}" for suggestion in self.suggestions)

        return "\n".join(feedback)

class TruthTableValidator:
    """Validate student-created truth tables"""

    def __init__(self):
        self.generator = TruthTableGenerator()

    def validate(self, expression: str, student_table: List[TruthTableRow]) -> ValidationResult:
        """Validate student's truth table against correct solution"""
        # Generate correct table
        correct_table = self.generator.generate(expression)

        errors = []
        suggestions = []
        correct_rows = 0

        # Check row count
        if len(student_table) != len(correct_table.rows):
            errors.append(
                f"Wrong number of rows: expected {len(correct_table.rows)}, got {len(student_table)}"
            )
            suggestions.append(
                f"With {len(correct_table.variables)} variables, you need 2^{len(correct_table.variables)} = {len(correct_table.rows)} rows"
            )

        # Check each row
        for i, (student_row, correct_row) in enumerate(zip(student_table, correct_table.rows)):
            # Check inputs match
            if student_row.inputs != correct_row.inputs:
                errors.append(f"Row {i}: Input values don't match expected combination")
                continue

            # Check output
            if student_row.output == correct_row.output:
                correct_rows += 1
            else:
                errors.append(
                    f"Row {i}: Expected output {int(correct_row.output)}, got {int(student_row.output)}"
                )

        # Identify pattern errors
        pattern_errors = self._identify_pattern_errors(student_table, correct_table)
        suggestions.extend(pattern_errors)

        is_correct = correct_rows == len(correct_table.rows) and len(student_table) == len(correct_table.rows)

        return ValidationResult(
            is_correct=is_correct,
            errors=errors,
            suggestions=suggestions,
            correct_rows=correct_rows,
            total_rows=len(correct_table.rows)
        )

    def _identify_pattern_errors(self, student_table: List[TruthTableRow], correct_table: TruthTable) -> List[str]:
        """Identify common pattern mistakes"""
        suggestions = []

        if len(student_table) != len(correct_table.rows):
            return suggestions

        # Check if student confused AND with OR
        and_or_confusion = sum(
            1 for s, c in zip(student_table, correct_table.rows)
            if s.output != c.output
        )

        if and_or_confusion > 0 and and_or_confusion < len(student_table):
            if "AND" in correct_table.expression and "OR" not in correct_table.expression:
                suggestions.append("Double-check the AND operation - both inputs must be 1")
            elif "OR" in correct_table.expression and "AND" not in correct_table.expression:
                suggestions.append("Double-check the OR operation - at least one input must be 1")

        return suggestions

3. Problem Generator Implementation

import random
from abc import ABC, abstractmethod

class ProblemType(Enum):
    DECIMAL_TO_BINARY = "decimal_to_binary"
    BINARY_TO_DECIMAL = "binary_to_decimal"
    BINARY_ADDITION = "binary_addition"
    SIMPLE_GATE = "simple_gate"
    TRUTH_TABLE_FROM_EXPRESSION = "truth_table_from_expression"
    EXPRESSION_SIMPLIFICATION = "expression_simplification"

class DifficultyLevel(Enum):
    BEGINNER = 1
    INTERMEDIATE = 2
    ADVANCED = 3

@dataclass
class Problem:
    id: str
    type: ProblemType
    difficulty: DifficultyLevel
    question: str
    solution: Any
    hints: List[str]
    metadata: Dict[str, Any]

    def check_answer(self, student_answer: Any) -> 'AnswerResult':
        """Check if student answer is correct"""
        # This will be implemented by problem type
        pass

    def get_hint(self, level: int) -> str:
        """Get hint at specified level (1-3)"""
        if level < 1 or level > len(self.hints):
            return "No more hints available"
        return self.hints[level - 1]

    def get_difficulty_score(self) -> float:
        """Get numerical difficulty score"""
        base_score = {
            DifficultyLevel.BEGINNER: 20,
            DifficultyLevel.INTERMEDIATE: 50,
            DifficultyLevel.ADVANCED: 80
        }
        return base_score[self.difficulty]

class ProblemGenerator:
    """Generate randomized practice problems"""

    def __init__(self, random_seed: Optional[int] = None):
        self.rng = random.Random(random_seed)
        self.used_problems = set()
        self.generators = {
            ProblemType.DECIMAL_TO_BINARY: BinaryConversionGenerator(self.rng),
            ProblemType.BINARY_TO_DECIMAL: BinaryConversionGenerator(self.rng),
            ProblemType.BINARY_ADDITION: BinaryConversionGenerator(self.rng),
            ProblemType.SIMPLE_GATE: LogicExpressionGenerator(self.rng),
            ProblemType.TRUTH_TABLE_FROM_EXPRESSION: LogicExpressionGenerator(self.rng),
        }

    def generate_problem(
        self,
        problem_type: ProblemType,
        difficulty: DifficultyLevel,
        constraints: Optional[Dict] = None
    ) -> Problem:
        """Generate a single problem"""
        generator = self.generators.get(problem_type)
        if not generator:
            raise ValueError(f"No generator for problem type: {problem_type}")

        problem = generator.generate(problem_type, difficulty, constraints or {})

        # Ensure uniqueness
        attempt = 0
        while problem.id in self.used_problems and attempt < 10:
            problem = generator.generate(problem_type, difficulty, constraints or {})
            attempt += 1

        self.used_problems.add(problem.id)
        return problem

class BinaryConversionGenerator:
    """Generate binary conversion problems"""

    def __init__(self, rng: random.Random):
        self.rng = rng
        self.hint_gen = HintGenerator()

    def generate(self, problem_type: ProblemType, difficulty: DifficultyLevel, constraints: Dict) -> Problem:
        """Generate binary conversion problem"""
        if problem_type == ProblemType.DECIMAL_TO_BINARY:
            return self._generate_decimal_to_binary(difficulty)
        elif problem_type == ProblemType.BINARY_TO_DECIMAL:
            return self._generate_binary_to_decimal(difficulty)
        elif problem_type == ProblemType.BINARY_ADDITION:
            return self._generate_binary_addition(difficulty)

    def _generate_decimal_to_binary(self, difficulty: DifficultyLevel) -> Problem:
        """Generate decimal to binary conversion problem"""
        # Difficulty determines number range
        ranges = {
            DifficultyLevel.BEGINNER: (1, 15),      # 4 bits
            DifficultyLevel.INTERMEDIATE: (16, 127),  # 7 bits
            DifficultyLevel.ADVANCED: (128, 255)     # 8 bits
        }

        min_val, max_val = ranges[difficulty]
        decimal_value = self.rng.randint(min_val, max_val)
        binary_solution = bin(decimal_value)[2:]  # Remove '0b' prefix

        problem = Problem(
            id=f"d2b_{decimal_value}_{difficulty.value}",
            type=ProblemType.DECIMAL_TO_BINARY,
            difficulty=difficulty,
            question=f"Convert {decimal_value} to binary",
            solution=binary_solution,
            hints=self.hint_gen.generate_decimal_to_binary_hints(decimal_value),
            metadata={"decimal": decimal_value, "bits": len(binary_solution)}
        )

        return problem

    def _generate_binary_to_decimal(self, difficulty: DifficultyLevel) -> Problem:
        """Generate binary to decimal conversion problem"""
        ranges = {
            DifficultyLevel.BEGINNER: (1, 15),
            DifficultyLevel.INTERMEDIATE: (16, 127),
            DifficultyLevel.ADVANCED: (128, 255)
        }

        min_val, max_val = ranges[difficulty]
        decimal_value = self.rng.randint(min_val, max_val)
        binary_value = bin(decimal_value)[2:]

        problem = Problem(
            id=f"b2d_{binary_value}_{difficulty.value}",
            type=ProblemType.BINARY_TO_DECIMAL,
            difficulty=difficulty,
            question=f"Convert {binary_value} to decimal",
            solution=decimal_value,
            hints=self.hint_gen.generate_binary_to_decimal_hints(binary_value),
            metadata={"binary": binary_value, "bits": len(binary_value)}
        )

        return problem

    def _generate_binary_addition(self, difficulty: DifficultyLevel) -> Problem:
        """Generate binary addition problem"""
        bit_widths = {
            DifficultyLevel.BEGINNER: 2,
            DifficultyLevel.INTERMEDIATE: 4,
            DifficultyLevel.ADVANCED: 8
        }

        width = bit_widths[difficulty]
        max_val = (2 ** width) - 1

        a = self.rng.randint(1, max_val // 2)
        b = self.rng.randint(1, max_val // 2)

        binary_a = bin(a)[2:].zfill(width)
        binary_b = bin(b)[2:].zfill(width)
        result = a + b
        binary_result = bin(result)[2:]

        problem = Problem(
            id=f"badd_{binary_a}_{binary_b}_{difficulty.value}",
            type=ProblemType.BINARY_ADDITION,
            difficulty=difficulty,
            question=f"What is {binary_a} + {binary_b} in binary?",
            solution=binary_result,
            hints=self.hint_gen.generate_binary_addition_hints(binary_a, binary_b),
            metadata={"operand1": binary_a, "operand2": binary_b, "carries": result > max_val}
        )

        return problem

class LogicExpressionGenerator:
    """Generate logic expression problems"""

    def __init__(self, rng: random.Random):
        self.rng = rng
        self.hint_gen = HintGenerator()

    def generate(self, problem_type: ProblemType, difficulty: DifficultyLevel, constraints: Dict) -> Problem:
        """Generate logic expression problem"""
        if problem_type == ProblemType.SIMPLE_GATE:
            return self._generate_simple_gate(difficulty)
        elif problem_type == ProblemType.TRUTH_TABLE_FROM_EXPRESSION:
            return self._generate_truth_table_problem(difficulty)

    def _generate_simple_gate(self, difficulty: DifficultyLevel) -> Problem:
        """Generate simple logic gate problem"""
        operators = ["AND", "OR", "XOR"]

        if difficulty == DifficultyLevel.BEGINNER:
            # Single gate: A AND B
            op = self.rng.choice(operators)
            expression = f"A {op} B"
            num_vars = 2
        elif difficulty == DifficultyLevel.INTERMEDIATE:
            # Two gates: A AND (B OR C)
            op1 = self.rng.choice(operators)
            op2 = self.rng.choice(operators)
            expression = f"A {op1} (B {op2} C)"
            num_vars = 3
        else:  # ADVANCED
            # Complex with NOT: NOT A AND (B OR (C XOR D))
            op1 = self.rng.choice(operators)
            op2 = self.rng.choice(operators)
            op3 = self.rng.choice(operators)
            expression = f"NOT A {op1} (B {op2} (C {op3} D))"
            num_vars = 4

        # Evaluate for specific input
        parser = ExpressionParser()
        variables = sorted(parser.extract_variables(expression))
        test_values = {v: self.rng.choice([True, False]) for v in variables}
        tree = parser.parse(expression)
        result = tree.evaluate(test_values)

        value_str = ", ".join(f"{v}={int(test_values[v])}" for v in variables)

        problem = Problem(
            id=f"gate_{expression}_{difficulty.value}",
            type=ProblemType.SIMPLE_GATE,
            difficulty=difficulty,
            question=f"Evaluate: {expression} when {value_str}",
            solution=int(result),
            hints=self.hint_gen.generate_logic_expression_hints(expression, test_values),
            metadata={"expression": expression, "variables": variables, "test_values": test_values}
        )

        return problem

    def _generate_truth_table_problem(self, difficulty: DifficultyLevel) -> Problem:
        """Generate truth table creation problem"""
        operators = ["AND", "OR", "XOR"]

        if difficulty == DifficultyLevel.BEGINNER:
            op = self.rng.choice(operators)
            expression = f"A {op} B"
        elif difficulty == DifficultyLevel.INTERMEDIATE:
            op1 = self.rng.choice(operators)
            op2 = self.rng.choice(operators)
            expression = f"(A {op1} B) {op2} C"
        else:  # ADVANCED
            op1 = self.rng.choice(operators)
            op2 = self.rng.choice(operators)
            expression = f"NOT (A {op1} B) {op2} C"

        # Generate solution
        generator = TruthTableGenerator()
        solution = generator.generate(expression)

        problem = Problem(
            id=f"tt_{expression}_{difficulty.value}",
            type=ProblemType.TRUTH_TABLE_FROM_EXPRESSION,
            difficulty=difficulty,
            question=f"Create a truth table for: {expression}",
            solution=solution,
            hints=self.hint_gen.generate_truth_table_hints(expression),
            metadata={"expression": expression, "num_rows": len(solution.rows)}
        )

        return problem

4. Hint System Implementation

class HintGenerator:
    """Generate progressive hints for problems"""

    def generate_decimal_to_binary_hints(self, decimal: int) -> List[str]:
        """Generate 3-level hints for decimal to binary conversion"""
        # Level 1: Concept reminder
        hint1 = "Convert by repeatedly dividing by 2 and tracking remainders"

        # Level 2: Show first step
        first_div = decimal // 2
        first_rem = decimal % 2
        hint2 = f"Start: {decimal} ÷ 2 = {first_div} remainder {first_rem}"

        # Level 3: Show most of solution
        binary = bin(decimal)[2:]
        shown = binary[:-2] if len(binary) > 2 else ""
        hint3 = f"The binary starts with {shown}... (complete the last 2 bits)"

        return [hint1, hint2, hint3]

    def generate_binary_to_decimal_hints(self, binary: str) -> List[str]:
        """Generate 3-level hints for binary to decimal conversion"""
        # Level 1: Concept reminder
        hint1 = "Each bit position represents a power of 2 (right to left: 2^0, 2^1, 2^2...)"

        # Level 2: Show position values
        powers = [f"2^{i}" for i in range(len(binary)-1, -1, -1)]
        hint2 = f"Positions are: {' '.join(powers)}"

        # Level 3: Show partial calculation
        decimal = int(binary, 2)
        partial_sum = sum(int(binary[i]) * (2 ** (len(binary)-1-i)) for i in range(len(binary)//2))
        hint3 = f"First half sums to {partial_sum}. Now add the rest..."

        return [hint1, hint2, hint3]

    def generate_binary_addition_hints(self, a: str, b: str) -> List[str]:
        """Generate 3-level hints for binary addition"""
        # Level 1: Concept reminder
        hint1 = "Add right to left, just like decimal. 1+1=10 (carry the 1)"

        # Level 2: Show first column
        last_a = int(a[-1])
        last_b = int(b[-1])
        last_sum = last_a + last_b
        hint2 = f"Rightmost column: {last_a} + {last_b} = {bin(last_sum)[2:]}"

        # Level 3: Show partial result
        result = bin(int(a, 2) + int(b, 2))[2:]
        shown = result[:-2] if len(result) > 2 else ""
        hint3 = f"Result starts with {shown}... (complete the last 2 bits)"

        return [hint1, hint2, hint3]

    def generate_logic_expression_hints(self, expression: str, values: Dict[str, bool]) -> List[str]:
        """Generate 3-level hints for logic expressions"""
        # Level 1: Concept reminder
        operations = []
        if "AND" in expression:
            operations.append("AND: both must be 1")
        if "OR" in expression:
            operations.append("OR: at least one must be 1")
        if "XOR" in expression:
            operations.append("XOR: exactly one must be 1")
        if "NOT" in expression:
            operations.append("NOT: flips the value")

        hint1 = "Remember: " + ", ".join(operations)

        # Level 2: Show evaluation order
        hint2 = "Evaluate from innermost parentheses outward, NOT operations first"

        # Level 3: Show partial evaluation
        parser = ExpressionParser()
        tree = parser.parse(expression)
        hint3 = f"After evaluating inner expressions, you should get a simpler form..."

        return [hint1, hint2, hint3]

    def generate_truth_table_hints(self, expression: str) -> List[str]:
        """Generate 3-level hints for truth table creation"""
        parser = ExpressionParser()
        variables = sorted(parser.extract_variables(expression))
        num_rows = 2 ** len(variables)

        # Level 1: Structure reminder
        hint1 = f"With {len(variables)} variables, you need {num_rows} rows (2^{len(variables)})"

        # Level 2: Show how to do one row
        first_values = {v: False for v in variables}
        tree = parser.parse(expression)
        first_result = tree.evaluate(first_values)
        value_str = ", ".join(f"{v}=0" for v in variables)
        hint2 = f"First row ({value_str}): {expression} = {int(first_result)}"

        # Level 3: Show partial table
        generator = TruthTableGenerator()
        table = generator.generate(expression)
        rows_to_show = min(len(table.rows) // 2, 4)
        partial = "\n".join(table.rows[i].to_string(variables) for i in range(rows_to_show))
        hint3 = f"First {rows_to_show} rows:\n{partial}\n(complete the rest)"

        return [hint1, hint2, hint3]

5. Quick Integration Example

# Complete working example

class BinaryEducationSystem:
    """Main system interface"""

    def __init__(self):
        self.truth_table_gen = TruthTableGenerator()
        self.problem_gen = ProblemGenerator()
        self.validator = TruthTableValidator()

    def demo_truth_table(self):
        """Demo: Generate and display a truth table"""
        expression = "A AND B OR NOT C"
        print(f"Expression: {expression}\n")

        table = self.truth_table_gen.generate(expression)
        print(table.to_text_table())
        print(f"\nMinterms: {', '.join(table.get_minterms())}")

    def demo_problem_generation(self):
        """Demo: Generate and solve a problem"""
        problem = self.problem_gen.generate_problem(
            ProblemType.DECIMAL_TO_BINARY,
            DifficultyLevel.BEGINNER
        )

        print(f"\nProblem: {problem.question}")
        print(f"Difficulty: {problem.difficulty.name}")
        print(f"\nHint 1: {problem.get_hint(1)}")
        print(f"Hint 2: {problem.get_hint(2)}")
        print(f"Hint 3: {problem.get_hint(3)}")
        print(f"\nSolution: {problem.solution}")

    def demo_validation(self):
        """Demo: Validate a student's truth table"""
        expression = "A AND B"

        # Student's (incorrect) answer
        student_table = [
            TruthTableRow({"A": False, "B": False}, False),
            TruthTableRow({"A": False, "B": True}, True),   # Wrong!
            TruthTableRow({"A": True, "B": False}, False),
            TruthTableRow({"A": True, "B": True}, True),
        ]

        result = self.validator.validate(expression, student_table)
        print(f"\n{result.get_detailed_feedback()}")

# Run demos
if __name__ == "__main__":
    system = BinaryEducationSystem()

    print("=" * 60)
    print("DEMO 1: Truth Table Generation")
    print("=" * 60)
    system.demo_truth_table()

    print("\n" + "=" * 60)
    print("DEMO 2: Problem Generation with Hints")
    print("=" * 60)
    system.demo_problem_generation()

    print("\n" + "=" * 60)
    print("DEMO 3: Answer Validation")
    print("=" * 60)
    system.demo_validation()

6. Testing Templates

# MVP-level testing (2-5 tests per component)

def test_expression_parser():
    """Test basic expression parsing"""
    parser = ExpressionParser()

    # Test 1: Simple expression
    tree = parser.parse("A AND B")
    assert tree.operator == "AND"

    # Test 2: Complex expression
    tree = parser.parse("(A OR B) AND NOT C")
    assert tree.operator == "AND"

    # Test 3: Evaluation
    tree = parser.parse("A AND B")
    result = tree.evaluate({"A": True, "B": False})
    assert result == False

def test_truth_table_generator():
    """Test truth table generation"""
    gen = TruthTableGenerator()

    # Test 1: Generate simple table
    table = gen.generate("A AND B")
    assert len(table.rows) == 4

    # Test 2: Verify outputs
    table = gen.generate("A OR B")
    true_count = sum(1 for row in table.rows if row.output)
    assert true_count == 3  # OR is true in 3 of 4 cases

def test_problem_generator():
    """Test problem generation"""
    gen = ProblemGenerator(random_seed=42)

    # Test 1: Generate decimal to binary
    problem = gen.generate_problem(
        ProblemType.DECIMAL_TO_BINARY,
        DifficultyLevel.BEGINNER
    )
    assert 1 <= int(problem.metadata["decimal"]) <= 15

    # Test 2: Hints are generated
    assert len(problem.hints) == 3

def test_validation():
    """Test answer validation"""
    validator = TruthTableValidator()

    # Test 1: Correct answer
    correct_table = [
        TruthTableRow({"A": False, "B": False}, False),
        TruthTableRow({"A": False, "B": True}, False),
        TruthTableRow({"A": True, "B": False}, False),
        TruthTableRow({"A": True, "B": True}, True),
    ]
    result = validator.validate("A AND B", correct_table)
    assert result.is_correct == True
    assert result.get_score() == 100.0

    # Test 2: Incorrect answer
    incorrect_table = [
        TruthTableRow({"A": False, "B": False}, True),  # Wrong
        TruthTableRow({"A": False, "B": True}, False),
        TruthTableRow({"A": True, "B": False}, False),
        TruthTableRow({"A": True, "B": True}, True),
    ]
    result = validator.validate("A AND B", incorrect_table)
    assert result.is_correct == False
    assert result.correct_rows == 3

Next Steps

  1. Copy templates into your project structure
  2. Run the demo to see the system in action
  3. Add persistence using SQLite or JSON files
  4. Build CLI for student interaction
  5. Add web interface (optional) using Flask or FastAPI

Each template is production-ready and follows the architecture specification. Customize as needed for your specific requirements.