299 lines
9 KiB
Markdown
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
|
|
```
|