191 lines
5.8 KiB
Markdown
191 lines
5.8 KiB
Markdown
# tssr — Tiny Soccer Simulator Rust
|
||
|
||
A blazing-fast CLI that generates completely random soccer matches on the spot — or run scripted matches with the `.tsrc` (Tiny Soccer Sim Rust C) language.
|
||
|
||
## Build
|
||
|
||
```bash
|
||
cargo build --release
|
||
```
|
||
|
||
The binary will be at `target/release/tssr`.
|
||
|
||
## Usage
|
||
|
||
```bash
|
||
./tssr # random match (table output)
|
||
./tssr --json # random match (JSON output)
|
||
./tssr --script match.tsrc # scripted match (table output)
|
||
./tssr --script match.tsrc --json # scripted match (JSON output)
|
||
./tssr --help # show usage info
|
||
```
|
||
|
||
### Shebang
|
||
|
||
`.tsrc` scripts can be run directly with a shebang:
|
||
|
||
```tsrc
|
||
#!/usr/bin/env tssr
|
||
string home = "Team A";
|
||
string away = "Team B";
|
||
// ...
|
||
match(home, away);
|
||
```
|
||
|
||
```bash
|
||
chmod +x match.tsrc
|
||
./match.tsrc
|
||
```
|
||
|
||
## Output
|
||
|
||
### Table mode (default)
|
||
|
||
```
|
||
╔════════════════════════════════════════╗
|
||
║ TSSR MATCH RESULT ║
|
||
╠════════════════════════════════════════╣
|
||
║ Man Page City 2 - 1 Libre Pool║
|
||
╠════════════════════════════════════════╣
|
||
║ ║
|
||
║ Shots 10 8 ║
|
||
║ On Target 4 3 ║
|
||
║ Possession 55% 45% ║
|
||
║ ║
|
||
╠════════════════════════════════════════╣
|
||
║ Goals: ║
|
||
║ Man Page City: Marco Smith (23') ║
|
||
║ Man Page City: Leo Brown (67') ║
|
||
║ Libre Pool: Dan Garcia (45') ║
|
||
╚════════════════════════════════════════╝
|
||
```
|
||
|
||
### JSON mode (`--json`)
|
||
|
||
```json
|
||
{
|
||
"home": {
|
||
"team": "Man Page City",
|
||
"score": 2,
|
||
"shots": 10,
|
||
"shots_on_target": 4,
|
||
"possession": 55,
|
||
"scorers": [
|
||
{"player": "Marco Smith", "minute": 23},
|
||
{"player": "Leo Brown", "minute": 67}
|
||
]
|
||
},
|
||
"away": {
|
||
"team": "Libre Pool",
|
||
"score": 1,
|
||
"shots": 8,
|
||
"shots_on_target": 3,
|
||
"possession": 45,
|
||
"scorers": [
|
||
{"player": "Dan Garcia", "minute": 45}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
## `.tsrc` Scripting Language
|
||
|
||
`.tsrc` (Tiny Soccer Sim Rust C) is a minimal C-like DSL for defining teams, players, and match events programmatically.
|
||
|
||
### Language Reference
|
||
|
||
```tsrc
|
||
// Declare a team variable
|
||
string var_name = "Team Name";
|
||
|
||
// Assign a player to a team (by variable or literal)
|
||
players["Player Name"] = var_name;
|
||
players["Player Name"] = "Team Name";
|
||
|
||
// Script a goal: minute (1-90), team (var or literal), player (literal)
|
||
goal(45, var_name, "Player Name");
|
||
goal(45, "Team Name", "Player Name");
|
||
|
||
// Select teams for the match (required)
|
||
match(var_home, var_away);
|
||
```
|
||
|
||
### Rules
|
||
|
||
- Variables must be declared with `string` before use
|
||
- `match()` directive is required — references variable names
|
||
- `players[]` references team names (values), not variable names — but variables are resolved automatically
|
||
- `goal()` arguments: `minute, team, player` — team can be variable or literal
|
||
- All semicolons are required
|
||
- `//` for single-line comments
|
||
- Strict validation: any inconsistency produces an error with a helpful message and exits immediately
|
||
|
||
### Validation Errors
|
||
|
||
The parser is strict — it will fail with a clear message if:
|
||
|
||
- A variable is used before being declared
|
||
- A variable is defined twice
|
||
- A player is added to a team that isn't in `match()`
|
||
- A player is added to the same team twice
|
||
- A goal references a player not on that team
|
||
- A goal references a team not in the match
|
||
- A goal minute is outside 1–90
|
||
- The same player scores at the same minute twice
|
||
- A match team has no players assigned
|
||
- `match()` references the same team for both home and away
|
||
|
||
### Scripted Goals
|
||
|
||
When `goal()` is used, the match becomes deterministic:
|
||
- Score is derived from the count of scripted goals per team
|
||
- Goal scorers and minutes are exactly as specified
|
||
- Shots and shots-on-target are floored at the goal count
|
||
- Possession remains randomized
|
||
|
||
Without `goal()`, everything is fully randomized.
|
||
|
||
## Team Names (Random Mode)
|
||
|
||
When no script is provided, teams are randomly selected from PES-style parodies referencing free software and open source:
|
||
|
||
- Man Page City, Libre Pool, ArsenAL, Shellsea, TomThumb FC
|
||
- Real Madread, BarcelON, BayErR, Door Mount, Inter Mission
|
||
- Man Unixited, A-JaX, CellTick, RanGERS, LaZIO, RomaAEM
|
||
- Port OS, BenFICA, Fine Node, AC M1Lan, PSGROOT
|
||
- Free BSD Athletic, Kernel Panic, Tor Valdes, Debian United
|
||
- Arch Lisbon, NixOS City, Fork Enoord, Branchand FC, Merge Conflict
|
||
|
||
## Stats Included
|
||
|
||
- Score
|
||
- Shots & shots on target
|
||
- Possession percentage
|
||
- Goal scorers with minute of goal
|
||
|
||
## Example Files
|
||
|
||
| File | Description |
|
||
|------|-------------|
|
||
| `scripted_match.tsrc` | GNU United vs BSD Athletic — 5 scripted goals with mixed variable/literal team references |
|
||
| `gnu-bsd-soccer.tsrc` | Variable-reference players, random goals |
|
||
| `test_match.tsrc` | Intentionally broken — demonstrates error handling |
|
||
|
||
## Testing
|
||
|
||
```bash
|
||
cargo test
|
||
```
|
||
|
||
13 integration tests cover:
|
||
- Goal validation (player on team, minute range, duplicates)
|
||
- Script validation (undefined variables, same team, missing match, no players)
|
||
- Happy paths (valid script, variable references, full scripted match)
|
||
|
||
## Performance
|
||
|
||
- Single runtime dependency (`rand`)
|
||
- `tempfile` is a dev-dependency only (for tests)
|
||
- Release profile uses LTO, single codegen unit, panic=abort, and symbol stripping
|
||
- Designed for sub-second cold compilation and instant execution
|
||
- Binary size: ~340KB stripped
|