2Brute2Best
πŸ”₯0
C8

Spell & Cheat-Code Mini Expression Language

Pro OnlyInterpreter
⚑ 60-Second Architecture SummaryC8 · Interpreter Pattern
Naive Bottleneck→Interpreter Pattern Cure
The Naive Bottleneck: Flawed operator precedence calculates wrong spell damage

Hardcoding quest conditions, combat formulas, and loot rules directly in C++ forces an engine recompile and binary patch for every balance tweak. Conversely, naive ad-hoc string parsing in the game loop fails on operator precedence, crashes on nested parentheses, and causes severe allocation churn inside 60 FPS combat.

Evaluating BASE + 3 * INT strictly left-to-right computes (BASE + 3) * INT (giving 230 instead of 50 for BASE=20, INT=10), drastically distorting combat balance.

The Pattern Cure: Interpreter Pattern

Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

We represent spell formulas as an Abstract Syntax Tree (AST) rooted at the Expr interface. Terminal expressions (NumberLiteral, StatVar) form leaf nodes that evaluate directly without recursion. Non-terminal expressions (AddExpr, MultiplyExpr) compose child expressions and evaluate binary operations. A FormulaParser compiles formula text into an AST once. During combat, casting a spell invokes ast->eval(statContext), evaluating tree nodes in nanoseconds.

Sprint 8 of 8 Β· Scripting Quest & Loot Rules With an Expression AST
Story C β€” 2D RPG Game Engine β€” Progressive System Build
1. Where our codebase left off
← C7

Across Sprints 1 through 7, we engineered our complete RPG Game Engine: stackable buff decorators (C1), nested composite inventories (C2), encapsulated memento checkpoints (C3), cloned prototype monster hordes (C4), memory-budgeted flyweight biomes (C5), decoupled bridge graphics backends (C6), and non-invasive visitor analytics passes (C7). Now we face our final engine milestone: data-driven gameplay scripting.

2. What we are building now

We need an expression language engine where combat designers and quest writers can write data-driven rules ("level >= 10 AND HAS_ITEM DragonKey") and dynamic spell damage formulas (BASE + 3 * INT, ( 10 + LEVEL ) * 2) in external config files, evaluated dynamically against runtime character attributes (StatContext).

3. The pain point we hit

Hardcoding quest conditions, combat formulas, and loot rules directly in C++ forces an engine recompile and binary patch for every balance tweak. Conversely, naive ad-hoc string parsing in the game loop fails on operator precedence, crashes on nested parentheses, and causes severe allocation churn inside 60 FPS combat.

4. What this unlocks next

The Interpreter Pattern parses formula and rule strings into an Abstract Syntax Tree (AST) of terminal (NumberLiteral, StatVar) and non-terminal (AddExpr, MultiplyExpr) nodes evaluated in nanoseconds against player state without recompilation. This completes our production-grade RPG Game Engine architecture!

01

The product ticket

Ticket Β· C8RPG-108 Β· Spell Damage and Combat Formula Mini Expression Language

Game designers need to write custom damage formulas for spells, buff scaling, and cheat codes without requesting engineering deployments for every formula balance tweak. Spells like Fireball calculate BASE + 3 * INT, while Frost Nova uses ( 10 + LEVEL ) * 2, evaluated dynamically against a hero's current StatContext (LEVEL, INT, STR, BASE).

Requirements
  • β€’

    Parse and evaluate mathematical formulas composed of integer constants, character stat identifiers (LEVEL, INT, STR, BASE), addition (+), and multiplication (*).

  • β€’

    Correctly observe standard operator precedence (* before +) and support nested parentheses ( ... ).

  • β€’

    Evaluate compiled expressions repeatedly against varying character StatContext snapshots with zero per-cast re-parsing overhead.

  • β€’

    Support graceful error handling and default fallbacks for undefined stats.

The next requirement that breaks naive code

Next sprint, combat design introduces item modifier formulas with subtraction (-) and division (/). The naive ad-hoc string scanner breaks completely on operator precedence and crashes whenever parentheses appear.

02

Step 1 β€” The naive code & why it breaks

The naive implementation splits formula strings with std::istringstream and computes values strictly left-to-right on every spell cast, ignoring arithmetic precedence and failing on parentheses.

Naive design (what not to ship) Β· naive.cpp
#include <cctype>
#include <print>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>

// Naive Spell Evaluator: parses string tokens strictly left-to-right without an AST.
// Completely fails operator precedence (evaluates 10 + 2 * 3 as (10+2)*3 = 36 instead of 16),
// cannot handle nested parentheses, and re-parses strings on every single spell cast.

class NaiveSpellEvaluator {
public:
    static int evaluate(const std::string& formula, const std::unordered_map<std::string, int>& stats) {
        std::istringstream stream(formula);
        std::string token;

        int currentVal = 0;
        char lastOp = '+';
        bool isFirst = true;

        while (stream >> token) {
            if (token == "+" || token == "*") {
                lastOp = token[0];
            } else {
                int operand = 0;
                if (std::isdigit(static_cast<unsigned char>(token[0]))) {
                    operand = std::stoi(token);
                } else {
                    auto it = stats.find(token);
                    operand = (it != stats.end()) ? it->second : 0;
                }

                if (isFirst) {
                    currentVal = operand;
                    isFirst = false;
                } else if (lastOp == '+') {
                    currentVal += operand;
                } else if (lastOp == '*') {
                    // Flawed left-to-right: ignores precedence!
                    currentVal *= operand;
                }
            }
        }
        return currentVal;
    }
};

int main() {
    std::unordered_map<std::string, int> heroStats{
        {"BASE", 20},
        {"INT", 10},
        {"LEVEL", 5}
    };

    // 1. Formula with precedence: BASE + 3 * INT
    // Expected math: 20 + (3 * 10) = 50.
    // Naive left-to-right: (20 + 3) * 10 = 230! (WRONG)
    std::string f1 = "BASE + 3 * INT";
    int res1 = NaiveSpellEvaluator::evaluate(f1, heroStats);
    std::println("Formula: '{}' -> Naive Result: {} (Expected: 50)", f1, res1);

    // 2. Parentheses are completely unsupported and will parse as 0 or crash
    std::string f2 = "( 10 + LEVEL ) * 2";
    int res2 = NaiveSpellEvaluator::evaluate(f2, heroStats);
    std::println("Formula with parentheses: '{}' -> Naive Result: {} (Broken!)", f2, res2);

    return 0;
}
Flawed operator precedence calculates wrong spell damage

Evaluating BASE + 3 * INT strictly left-to-right computes (BASE + 3) * INT (giving 230 instead of 50 for BASE=20, INT=10), drastically distorting combat balance.

Total failure on nested parentheses

Parentheses like ( 10 + LEVEL ) * 2 cannot be evaluated with a simple sequential accumulator without an Abstract Syntax Tree (AST) or operator stack.

Severe CPU overhead from string re-parsing

Splitting and scanning formula strings on every cast inside a 60 FPS multiplayer combat loop produces hundreds of unnecessary heap allocations per second.

03

Step 2 β€” The fix: Interpreter Pattern

Core idea in one sentence

Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.

We represent spell formulas as an Abstract Syntax Tree (AST) rooted at the Expr interface. Terminal expressions (NumberLiteral, StatVar) form leaf nodes that evaluate directly without recursion. Non-terminal expressions (AddExpr, MultiplyExpr) compose child expressions and evaluate binary operations. A FormulaParser compiles formula text into an AST once. During combat, casting a spell invokes ast->eval(statContext), evaluating tree nodes in nanoseconds.

Pattern roles in our story
Abstract Expression

Expr β€” declares pure virtual eval(const StatContext& ctx) const = 0; and a debugging repr() string method.

Terminal Expressions

NumberLiteral and StatVar β€” evaluate leaf values (integer constants and variable lookups) without recursing.

Non-Terminal Expressions

AddExpr and MultiplyExpr β€” composite expressions that own left_ and right_ child expressions and combine their results.

Context

StatContext β€” maintains key-value mappings of runtime character attributes (BASE, INT, LEVEL, STR).

Client / Parser

SpellFormula and FormulaParser β€” tokenize formula strings into the composite AST structure once at initialization.

Brute2Best Pro Content

Unlock Full UML Diagram & C++23 Solution

The interactive UML architecture diagrams, refactored C++23 code, real-world extensions, and interview Q&A are part of Brute2Best Pro. All 7 foundation chapters are 100% free.

All 440 visual dry runs & algorithms
Interactive step-by-step playback with pointer & array state transitions
52 comprehensive LLD chapters
Interactive UML diagrams, real-world stories (Food Delivery, Trading, RPG), and practice solutions
40 C++ basics labs & hardware memory models
Vtables, byte layouts, assembly insights, and multi-threading race conditions
Production-ready C++23 source code
Modern idioms, clean contracts, time/space complexity derivations, and interview takeaways

First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.

Systems Integration

Related C++ Systems & Algorithmic Foundations

Deep dive into underlying layers