tiny-soccersim-rust/docs/tsrc-language/README.md

299 lines
9 KiB
Markdown

# `.tsrc` Language Reference
`.tsrc` (Tiny Soccer Sim Rust C) is a minimal C-like DSL for scripting soccer matches in tssr. It lets you define teams, assign players, script goals, and control output format — all from a plain text file.
## Invocation
### Via `--script` flag
```bash
tssr --script match.tsrc # table output
tssr --script match.tsrc --json # JSON output
```
### Via shebang
Add this as the first line of your `.tsrc` file:
```tsrc
#!/usr/bin/env tssr
```
Then make it executable and run directly:
```bash
chmod +x match.tsrc
./match.tsrc
```
## Language Syntax
### Team Declaration
```tsrc
string var_name = "Team Name";
```
Declares a variable that holds a team name. Variables can be referenced later in `players[]`, `goal()`, and `match()`.
**Rules:**
- Variable names must be alphanumeric + underscores
- Duplicate variable names are an error
- The string value is the team's display name
### Player Assignment
```tsrc
players["Player Name"] = var_name;
players["Player Name"] = "Team Name";
```
Assigns a player to a team. The right side can be either a previously declared variable or a string literal.
**Rules:**
- The team (right side) must resolve to a team that appears in `match()`
- The same player cannot be added to the same team twice
- Variables must be declared before use (forward references are an error)
### Scripted Goals
```tsrc
goal(minute, team, player);
```
Scripts a specific goal event. When any `goal()` appears, the match score becomes deterministic.
| Argument | Type | Description |
|----------|------|-------------|
| `minute` | integer (1-90) | Minute the goal was scored |
| `team` | variable or string | The team that scored |
| `player` | string literal | The player who scored |
**Rules:**
- Minute must be between 1 and 90
- Team must be one of the two match teams
- Player must be on that team's roster
- The same player cannot score at the same minute twice
- Variables must be declared before use
### Match Selection
```tsrc
match(var_home, var_away);
```
Selects which two teams play. **Required** — every `.tsrc` script must contain exactly one `match()`.
**Rules:**
- Both variables must be previously declared
- Home and away must be different teams
- Both teams must have at least one player assigned
### Output Mode Directive
```tsrc
#define JSON
#define ASCII_TABLE
#define UNICODE_TABLE
```
Sets the output format for the match. Optional — defaults to `UNICODE_TABLE`.
**Rules:**
- Only one output mode `#define` per script
- Must be one of the three known modes
- Unknown names produce an error
The `--json` CLI flag always overrides the script's `#define`.
### Situation Mode Directive
```tsrc
#define SITUATION
```
Enables hybrid deterministic/stochastic mode. Scripted goals are guaranteed, but additional random goals (up to 5 per team) are also generated.
**Rules:**
- Requires at least one `goal()` call — error without one
- Can be combined with any output mode `#define`
- Scripted goals always appear; random goals vary per run
- Random goals are drawn from the team's roster (or name pools if roster is empty)
## Complete Grammar
```
script = statement*
statement = define | string_decl | player_insert | goal_stmt | match_stmt
define = "#define" (identifier | "SITUATION")
string_decl = "string" identifier "=" string_literal ";"
player_insert = "players" "[" string_literal "]" "=" (identifier | string_literal) ";"
goal_stmt = "goal" "(" number "," (identifier | string_literal) "," string_literal ")" ";"
match_stmt = "match" "(" identifier "," identifier ")" ";"
comment = "//" .* "\n"
shebang = "#!" .* "\n"
```
## Output Modes
| Mode | `#define` | CLI flag | Description |
|------|-----------|----------|-------------|
| Unicode table | `UNICODE_TABLE` (default) | — | Box-drawing characters (`╔`, `═`, `║`) |
| ASCII table | `ASCII_TABLE` | — | ASCII-only (`+`, `-`, `|`) |
| JSON | `JSON` | `--json` | Machine-readable JSON |
## Match Generation Modes
| Mode | `#define` | Behavior |
|------|-----------|----------|
| Random | (none) | Everything is randomized |
| Scripted | (none, with `goal()`) | Goals, scorers, minutes are exact |
| Situation | `SITUATION` | Scripted goals guaranteed + 0-5 random extra goals per team |
## Scripted Goals Behavior
### Deterministic Mode (goal() without SITUATION)
When `goal()` is used, the match becomes deterministic:
| Field | Behavior with `goal()` | Behavior without |
|-------|------------------------|-------------------|
| Score | Count of goals per team | Random 0-5 |
| Scorers | Exact `(player, minute)` from script | Random names + minutes |
| Shots | `max(score, random(4..=18))` — floor = goals | Random 4-18 |
| Shots on target | `max(score, random(1..shots))` | Random |
| Possession | Still random | Random |
### Situation Mode (goal() with SITUATION)
Hybrid deterministic/stochastic — scripted goals are guaranteed, plus random extras:
| Field | Behavior in SITUATION mode |
|-------|---------------------------|
| Score | Scripted goals + 0-5 random extra per team |
| Scorers | Exact scripted goals + random from roster/name pools |
| Shots | `max(total_score, random(4..=18))` |
| Shots on target | `max(total_score, random(1..shots))` |
| Possession | Still random |
## Variable Resolution
Variables are resolved at parse time by looking up their declared value:
```tsrc
string home = "GNU United";
string away = "BSD Athletic";
players["Richard Stallman"] = home; // resolves to "GNU United"
players["Theo de Raadt"] = away; // resolves to "BSD Athletic"
goal(12, home, "Richard Stallman"); // goal for "GNU United"
goal(34, "BSD Athletic", "Theo de Raadt"); // literal also works
match(home, away); // GNU United vs BSD Athletic
```
You can mix variables and literals freely. Forward references (using a variable before its `string` declaration) are an error.
## Validation
The parser is strict. Any inconsistency produces an error and exits immediately.
### Error Conditions
| Error | Cause |
|-------|-------|
| `undefined variable 'x'` | Variable used before `string x = ...` declaration |
| `variable 'x' redefined` | Two `string x = ...` declarations |
| `players[...] = "X" which is not a match team` | Roster team isn't in `match()` |
| `duplicate player "X" on team "Y"` | Same player added to same team twice |
| `"X" has no players assigned` | A match team has zero players |
| `match() has the same team for both home and away` | Both `match()` args resolve to same team |
| `no match() directive found` | Missing `match()` |
| `goal() minute must be between 1 and 90` | Invalid minute |
| `player "X" is not on team "Y"` | Goal references unregistered player |
| `duplicate goal` | Same player scores same minute twice |
| `undefined variable 'x' in goal()` | Goal uses undeclared team variable |
| `duplicate #define` | More than one output mode `#define` |
| `unknown #define 'FOO'` | Unrecognized output mode |
| `#define SITUATION requires at least one goal() call` | SITUATION defined without any `goal()` |
## Full Example
```tsrc
#!/usr/bin/env tssr
// GNU United vs BSD Athletic
#define ASCII_TABLE
string home = "GNU United";
string away = "BSD Athletic";
players["Richard Stallman"] = home;
players["Brian Fox"] = home;
players["Linus Torvalds"] = home;
players["Theo de Raadt"] = away;
players["Jordan Hubbard"] = away;
players["Ken Thompson"] = away;
goal(12, home, "Richard Stallman");
goal(34, away, "Theo de Raadt");
goal(56, home, "Brian Fox");
goal(78, "BSD Athletic", "Ken Thompson");
goal(89, home, "Linus Torvalds");
match(home, away);
```
**Output:**
```
+------------------------------------+
+ TSSR MATCH RESULT +
+------------------------------------+
+ GNU United 3-2 BSD Athletic +
+------------------------------------+
+ +
+Shots 4 5 +
+On Target 3 2 +
+Possession 48% 52% +
+ +
+Goals: +
+GNU United: Richard Stallman (12') +
+GNU United: Brian Fox (56') +
+GNU United: Linus Torvalds (89') +
+BSD Athletic: Theo de Raadt (34') +
+BSD Athletic: Ken Thompson (78') +
+------------------------------------+
```
## Situation Mode Example
```tsrc
#!/usr/bin/env tssr
// Hybrid mode: some goals guaranteed, others random
#define SITUATION
string home = "GNU United";
string away = "BSD Athletic";
players["Richard Stallman"] = home;
players["Brian Fox"] = home;
players["Linus Torvalds"] = home;
players["Theo de Raadt"] = away;
players["Jordan Hubbard"] = away;
players["Ken Thompson"] = away;
goal(12, home, "Richard Stallman"); // guaranteed
goal(67, away, "Theo de Raadt"); // guaranteed
// Up to 5 additional random goals per team will be added
match(home, away);
```
Each run produces different scores — scripted goals always appear, random goals vary:
```
Run 1: GNU United 2-3 BSD Athletic
Run 2: GNU United 1-2 BSD Athletic
Run 3: GNU United 2-2 BSD Athletic
```