uCalc API Version: 2.1.3-preview.2 Released: 6/16/2026
Warning
uCalc API Preview Release Notice:The documentation describes the intended behavior of the API. The current preview build contains incomplete features, unoptimized performance, and is subject to breaking changes.
Project: Creating a DSL for Board Game Rules
Product:
Class:
A step-by-step project to build a simple Domain-Specific Language (DSL) for board game rules using the uCalc Transformer.
Remarks
🎲 Project: Building a DSL for Board Game Rules
This project will guide you through building a simple but powerful Domain-Specific Language (DSL) for processing board game actions. It's a perfect real-world example of how the declarative power of the uCalc.Transformer can create a readable, maintainable, and easily modifiable rules engine.
The Goal: A Readable Game Script
Instead of hardcoding complex if/else or switch statements in your application, we want to process a simple, human-readable script that represents a player's turn:
PLAYER 1 MOVES 5 SPACESPLAYER 2 GAINS 10 GOLDPLAYER 1 DRAWS 2 CARDSThis script is far more intuitive and can be easily modified for different game variants or expansions without recompiling the main application.
The Strategy: Transforming DSL into uCalc Expressions
The key to building our DSL is to teach the uCalc engine to understand our new keywords (MOVE, GAIN, DRAW). We'll use the ExpressionTransformer to transform our DSL syntax into standard mathematical expressions before they are evaluated.
For example, the line PLAYER 1 MOVES 5 SPACES will be automatically rewritten to player1_position = player1_position + 5.
Step 1: Setting Up the Game State
First, we need variables within our uCalc instance to represent the game state. We'll create these using DefineVariable().
// Player 1's stateuc.DefineVariable("player1_position = 0");uc.DefineVariable("player1_gold = 20");uc.DefineVariable("player1_cards = 5");// Player 2's stateuc.DefineVariable("player2_position = 0");uc.DefineVariable("player2_gold = 20");uc.DefineVariable("player2_cards = 5");Step 2: Defining the DSL Rules
Next, we'll get the ExpressionTransformer and add rules for each of our keywords using FromTo(). We use the {@Number} category matcher to capture the dynamic values for the player number and the action amount.
// Get the transformer that pre-processes expressions.var t = uc.ExpressionTransformer;// Rule for MOVEt.FromTo("PLAYER {@Number:p} MOVES {@Number:n} SPACES", "player{p}_position = player{p}_position + {n}");// Rule for GAINt.FromTo("PLAYER {@Number:p} GAINS {@Number:n} GOLD", "player{p}_gold = player{p}_gold + {n}");// Rule for DRAWt.FromTo("PLAYER {@Number:p} DRAWS {@Number:n} CARDS", "player{p}_cards = player{p}_cards + {n}");Step 3: Processing a Game Turn
With our transformation rules in place, we can now pass an entire multi-line script to EvalStr(). Because newlines are treated as statement separators by default, uCalc will automatically pre-process and execute each line sequentially.
After the script runs, we can inspect the variables to see the final game state.
⚖️ Why uCalc? (Comparative Analysis)
- vs. Hardcoding Logic: A hardcoded approach is rigid. If a rule changes (e.g., a special space doubles the gold gained), you must recompile the application. A DSL allows rules to be loaded from a text file, making the game easily modifiable for house rules or expansions.
- vs. Full Scripting Engine (Lua/Python): Embedding a full scripting engine is heavyweight and complex. uCalc provides a lightweight, sandboxed environment perfect for this kind of task-specific language. The ExpressionTransformer is a simpler tool for this job than writing a full parser for the DSL from scratch.
Examples
A minimal example of a DSL command to move a player piece.
using uCalcSoftware;
var uc = new uCalc();
uc.DefineVariable("player1_position = 0");
// Define a rule to translate the MOVE command
var t = uc.ExpressionTransformer;
t.FromTo("PLAYER {@Number:p} MOVES {@Number:n} SPACES", "player{p}_position = player{p}_position + {n}");
// Execute a single command
uc.EvalStr("PLAYER 1 MOVES 5 SPACES");
Console.WriteLine($"Player 1 is now at position: {uc.EvalStr("player1_position")}");
Player 1 is now at position: 5 using uCalcSoftware; var uc = new uCalc(); uc.DefineVariable("player1_position = 0"); // Define a rule to translate the MOVE command var t = uc.ExpressionTransformer; t.FromTo("PLAYER {@Number:p} MOVES {@Number:n} SPACES", "player{p}_position = player{p}_position + {n}"); // Execute a single command uc.EvalStr("PLAYER 1 MOVES 5 SPACES"); Console.WriteLine($"Player 1 is now at position: {uc.EvalStr("player1_position")}");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
int main() {
uCalc uc;
uc.DefineVariable("player1_position = 0");
// Define a rule to translate the MOVE command
auto t = uc.ExpressionTransformer();
t.FromTo("PLAYER {@Number:p} MOVES {@Number:n} SPACES", "player{p}_position = player{p}_position + {n}");
// Execute a single command
uc.EvalStr("PLAYER 1 MOVES 5 SPACES");
cout << "Player 1 is now at position: " << uc.EvalStr("player1_position") << endl;
}
Player 1 is now at position: 5 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; int main() { uCalc uc; uc.DefineVariable("player1_position = 0"); // Define a rule to translate the MOVE command auto t = uc.ExpressionTransformer(); t.FromTo("PLAYER {@Number:p} MOVES {@Number:n} SPACES", "player{p}_position = player{p}_position + {n}"); // Execute a single command uc.EvalStr("PLAYER 1 MOVES 5 SPACES"); cout << "Player 1 is now at position: " << uc.EvalStr("player1_position") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub Main()
Dim uc As New uCalc()
uc.DefineVariable("player1_position = 0")
'// Define a rule to translate the MOVE command
Dim t = uc.ExpressionTransformer
t.FromTo("PLAYER {@Number:p} MOVES {@Number:n} SPACES", "player{p}_position = player{p}_position + {n}")
'// Execute a single command
uc.EvalStr("PLAYER 1 MOVES 5 SPACES")
Console.WriteLine($"Player 1 is now at position: {uc.EvalStr("player1_position")}")
End Sub
End Module
Player 1 is now at position: 5 Imports System Imports uCalcSoftware Public Module Program Public Sub Main() Dim uc As New uCalc() uc.DefineVariable("player1_position = 0") '// Define a rule to translate the MOVE command Dim t = uc.ExpressionTransformer t.FromTo("PLAYER {@Number:p} MOVES {@Number:n} SPACES", "player{p}_position = player{p}_position + {n}") '// Execute a single command uc.EvalStr("PLAYER 1 MOVES 5 SPACES") Console.WriteLine($"Player 1 is now at position: {uc.EvalStr("player1_position")}") End Sub End Module
A complete game turn script demonstrating multiple DSL commands for moving, gaining resources, and drawing cards.
using uCalcSoftware;
var uc = new uCalc();
// 1. Define the initial game state
uc.DefineVariable("p1_pos = 0");
uc.DefineVariable("p1_gold = 10");
uc.DefineVariable("p2_pos = 0");
uc.DefineVariable("p2_gold = 10");
// 2. Define the DSL rules
var t = uc.ExpressionTransformer;
t.FromTo("PLAYER {@Number:p} MOVES {@Number:n}", "p{p}_pos = p{p}_pos + {n}");
t.FromTo("PLAYER {@Number:p} GAINS {@Number:n} GOLD", "p{p}_gold = p{p}_gold + {n}");
// 3. Define the script for the turn
var game_turn_script = """
PLAYER 1 MOVES 4
PLAYER 2 MOVES 2
PLAYER 1 GAINS 5 GOLD
PLAYER 2 MOVES 3
""";
// 4. Execute the script
uc.EvalStr(game_turn_script);
// 5. Display the final state
Console.WriteLine("--- End of Turn State ---");
Console.WriteLine($"Player 1 Position: {uc.EvalStr("p1_pos")}");
Console.WriteLine($"Player 1 Gold: {uc.EvalStr("p1_gold")}");
Console.WriteLine($"Player 2 Position: {uc.EvalStr("p2_pos")}");
Console.WriteLine($"Player 2 Gold: {uc.EvalStr("p2_gold")}");
--- End of Turn State ---
Player 1 Position: 4
Player 1 Gold: 15
Player 2 Position: 5
Player 2 Gold: 10 using uCalcSoftware; var uc = new uCalc(); // 1. Define the initial game state uc.DefineVariable("p1_pos = 0"); uc.DefineVariable("p1_gold = 10"); uc.DefineVariable("p2_pos = 0"); uc.DefineVariable("p2_gold = 10"); // 2. Define the DSL rules var t = uc.ExpressionTransformer; t.FromTo("PLAYER {@Number:p} MOVES {@Number:n}", "p{p}_pos = p{p}_pos + {n}"); t.FromTo("PLAYER {@Number:p} GAINS {@Number:n} GOLD", "p{p}_gold = p{p}_gold + {n}"); // 3. Define the script for the turn var game_turn_script = """ PLAYER 1 MOVES 4 PLAYER 2 MOVES 2 PLAYER 1 GAINS 5 GOLD PLAYER 2 MOVES 3 """; // 4. Execute the script uc.EvalStr(game_turn_script); // 5. Display the final state Console.WriteLine("--- End of Turn State ---"); Console.WriteLine($"Player 1 Position: {uc.EvalStr("p1_pos")}"); Console.WriteLine($"Player 1 Gold: {uc.EvalStr("p1_gold")}"); Console.WriteLine($"Player 2 Position: {uc.EvalStr("p2_pos")}"); Console.WriteLine($"Player 2 Gold: {uc.EvalStr("p2_gold")}");
#include
#include "uCalc.h"
using namespace std;
using namespace uCalcSoftware;
int main() {
uCalc uc;
// 1. Define the initial game state
uc.DefineVariable("p1_pos = 0");
uc.DefineVariable("p1_gold = 10");
uc.DefineVariable("p2_pos = 0");
uc.DefineVariable("p2_gold = 10");
// 2. Define the DSL rules
auto t = uc.ExpressionTransformer();
t.FromTo("PLAYER {@Number:p} MOVES {@Number:n}", "p{p}_pos = p{p}_pos + {n}");
t.FromTo("PLAYER {@Number:p} GAINS {@Number:n} GOLD", "p{p}_gold = p{p}_gold + {n}");
// 3. Define the script for the turn
auto game_turn_script = R"(
PLAYER 1 MOVES 4
PLAYER 2 MOVES 2
PLAYER 1 GAINS 5 GOLD
PLAYER 2 MOVES 3
)";
// 4. Execute the script
uc.EvalStr(game_turn_script);
// 5. Display the final state
cout << "--- End of Turn State ---" << endl;
cout << "Player 1 Position: " << uc.EvalStr("p1_pos") << endl;
cout << "Player 1 Gold: " << uc.EvalStr("p1_gold") << endl;
cout << "Player 2 Position: " << uc.EvalStr("p2_pos") << endl;
cout << "Player 2 Gold: " << uc.EvalStr("p2_gold") << endl;
}
--- End of Turn State ---
Player 1 Position: 4
Player 1 Gold: 15
Player 2 Position: 5
Player 2 Gold: 10 #include <iostream> #include "uCalc.h" using namespace std; using namespace uCalcSoftware; int main() { uCalc uc; // 1. Define the initial game state uc.DefineVariable("p1_pos = 0"); uc.DefineVariable("p1_gold = 10"); uc.DefineVariable("p2_pos = 0"); uc.DefineVariable("p2_gold = 10"); // 2. Define the DSL rules auto t = uc.ExpressionTransformer(); t.FromTo("PLAYER {@Number:p} MOVES {@Number:n}", "p{p}_pos = p{p}_pos + {n}"); t.FromTo("PLAYER {@Number:p} GAINS {@Number:n} GOLD", "p{p}_gold = p{p}_gold + {n}"); // 3. Define the script for the turn auto game_turn_script = R"( PLAYER 1 MOVES 4 PLAYER 2 MOVES 2 PLAYER 1 GAINS 5 GOLD PLAYER 2 MOVES 3 )"; // 4. Execute the script uc.EvalStr(game_turn_script); // 5. Display the final state cout << "--- End of Turn State ---" << endl; cout << "Player 1 Position: " << uc.EvalStr("p1_pos") << endl; cout << "Player 1 Gold: " << uc.EvalStr("p1_gold") << endl; cout << "Player 2 Position: " << uc.EvalStr("p2_pos") << endl; cout << "Player 2 Gold: " << uc.EvalStr("p2_gold") << endl; }
Imports System
Imports uCalcSoftware
Public Module Program
Public Sub Main()
Dim uc As New uCalc()
'// 1. Define the initial game state
uc.DefineVariable("p1_pos = 0")
uc.DefineVariable("p1_gold = 10")
uc.DefineVariable("p2_pos = 0")
uc.DefineVariable("p2_gold = 10")
'// 2. Define the DSL rules
Dim t = uc.ExpressionTransformer
t.FromTo("PLAYER {@Number:p} MOVES {@Number:n}", "p{p}_pos = p{p}_pos + {n}")
t.FromTo("PLAYER {@Number:p} GAINS {@Number:n} GOLD", "p{p}_gold = p{p}_gold + {n}")
'// 3. Define the script for the turn
Dim game_turn_script = "
PLAYER 1 MOVES 4
PLAYER 2 MOVES 2
PLAYER 1 GAINS 5 GOLD
PLAYER 2 MOVES 3
"
'// 4. Execute the script
uc.EvalStr(game_turn_script)
'// 5. Display the final state
Console.WriteLine("--- End of Turn State ---")
Console.WriteLine($"Player 1 Position: {uc.EvalStr("p1_pos")}")
Console.WriteLine($"Player 1 Gold: {uc.EvalStr("p1_gold")}")
Console.WriteLine($"Player 2 Position: {uc.EvalStr("p2_pos")}")
Console.WriteLine($"Player 2 Gold: {uc.EvalStr("p2_gold")}")
End Sub
End Module
--- End of Turn State ---
Player 1 Position: 4
Player 1 Gold: 15
Player 2 Position: 5
Player 2 Gold: 10 Imports System Imports uCalcSoftware Public Module Program Public Sub Main() Dim uc As New uCalc() '// 1. Define the initial game state uc.DefineVariable("p1_pos = 0") uc.DefineVariable("p1_gold = 10") uc.DefineVariable("p2_pos = 0") uc.DefineVariable("p2_gold = 10") '// 2. Define the DSL rules Dim t = uc.ExpressionTransformer t.FromTo("PLAYER {@Number:p} MOVES {@Number:n}", "p{p}_pos = p{p}_pos + {n}") t.FromTo("PLAYER {@Number:p} GAINS {@Number:n} GOLD", "p{p}_gold = p{p}_gold + {n}") '// 3. Define the script for the turn Dim game_turn_script = " PLAYER 1 MOVES 4 PLAYER 2 MOVES 2 PLAYER 1 GAINS 5 GOLD PLAYER 2 MOVES 3 " '// 4. Execute the script uc.EvalStr(game_turn_script) '// 5. Display the final state Console.WriteLine("--- End of Turn State ---") Console.WriteLine($"Player 1 Position: {uc.EvalStr("p1_pos")}") Console.WriteLine($"Player 1 Gold: {uc.EvalStr("p1_gold")}") Console.WriteLine($"Player 2 Position: {uc.EvalStr("p2_pos")}") Console.WriteLine($"Player 2 Gold: {uc.EvalStr("p2_gold")}") End Sub End Module