Add docs, new define directive and ASCII table output as an option
This commit is contained in:
parent
bf5f448897
commit
62c941b4e8
12 changed files with 1215 additions and 106 deletions
|
|
@ -3,6 +3,7 @@ name = "tssr"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Tiny Soccer Simulator Rust - generates random soccer matches on the spot"
|
||||
authors = ["Mark Robillard Jr (MARKMENTAL)"]
|
||||
|
||||
[lib]
|
||||
name = "tssr"
|
||||
|
|
|
|||
456
LICENSE
Normal file
456
LICENSE
Normal file
|
|
@ -0,0 +1,456 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other works are designed to take
|
||||
away your freedom to share and change the works. By contrast, the GNU
|
||||
General Public License is intended to guarantee your freedom to share
|
||||
and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source or can get it for free if
|
||||
you receive it, that you can change the software or use pieces of it in
|
||||
new free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software: if you
|
||||
modify it, you must pass on the same freedoms to the recipients.
|
||||
Responsibilities include passing along copyright notices, license text,
|
||||
and noting any changes.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For developers' and users' protection, the GPL clearly explains that
|
||||
there is no warranty for this free software. Both users' and
|
||||
developers' interests require that the software be maintained, and the
|
||||
GPL requires that modified versions be marked as changed so that
|
||||
problems will not be attributed erroneously to previous authors.
|
||||
|
||||
Finally, the GPL requires that software patents cannot be used to
|
||||
render the non-free program effectively proprietary. To prevent this,
|
||||
the GPL promises that patents cannot be used to restrict the making,
|
||||
using, or selling of a program, or to restrict the freedoms that the
|
||||
GPL guarantees.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "You" and "your". "Licensees"
|
||||
and "recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you infringe under applicable copyright law,
|
||||
except executing it on a computer or modifying a private copy.
|
||||
Propagation includes copying, distribution (with or without making
|
||||
the work available to the public), making available in some countries,
|
||||
and other acts of public distribution.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or modify copies. Mere interaction with a computer
|
||||
network, without transfer of a copy, does not convey a work.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominent feature that
|
||||
(1) displays an appropriate copyright notice, and (2) tells the user
|
||||
that there is no warranty for the work (except to the extent that
|
||||
warranties are provided), that the licensees may convey the work under
|
||||
this License, and how to view a copy of this License. If the interface
|
||||
presents a specific list of user commands or options, such as a menu,
|
||||
that satisfies this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an
|
||||
established standard recognized by an international standards body, or,
|
||||
in the case of interfaces for a given programming language, one that
|
||||
is widely used among developers working in that language. The standard
|
||||
interface for a function call is its name, parameter list, and return
|
||||
value type.
|
||||
|
||||
The "System Libraries" for a work include anything, other than the
|
||||
work as a whole, that (a) is included in the normal manner that the
|
||||
operating system is distributed in, and (b) that makes the major
|
||||
components of the operating system (such as the kernel, window system,
|
||||
and command-line shells) function, but which are not themselves
|
||||
considered major components of the system. A "System Library" is
|
||||
anything included with the work that can be considered the operating
|
||||
system's compiler, or a major compiler used to produce the work, or the
|
||||
dynamic linker used to load the work for execution.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to make modifications to the work,
|
||||
including scripts to control those activities. However, it does not
|
||||
include the work's System Libraries, or general-purpose tools or
|
||||
generally available free programs which are used unmodified in
|
||||
performing those activities but which are not part of the work. For
|
||||
example, Corresponding Source includes interface definition files
|
||||
associated with source files for the work, and the shared libraries
|
||||
and dynamic linker scripts for an executable work.
|
||||
|
||||
The Corresponding Source need not include anything that users can
|
||||
regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the copyright
|
||||
of the Program, and are irrevocable provided the stated conditions are
|
||||
met. This License explicitly affirms your unlimited permission to run
|
||||
the unmodified Program. The output from running a covered work is
|
||||
covered by this License only if the output, given its content,
|
||||
constitutes a covered work. This License acknowledges your rights of
|
||||
fair use or other equivalent, as provided by any copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey,
|
||||
without conditions so long as your license otherwise remains in force.
|
||||
You may convey covered works to others for the sole purpose of having
|
||||
them make modifications exclusively for you, or providing you with
|
||||
facilities for running those works, provided that you comply with the
|
||||
terms of this License in conveying all material for which you do not
|
||||
control copyright. Those thus making or running the covered works for
|
||||
you must do so exclusively on your behalf, under your direction and
|
||||
control, on terms that prohibit them from making any additional use of
|
||||
the copyrighted material.
|
||||
|
||||
Conveying under any other permission is permitted solely under the
|
||||
conditions stated below. Sublicensing is not permitted; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 26 May 1996, or under any
|
||||
similar law prohibiting or restricting circumvention of such measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to
|
||||
prohibit circumvention of technological measures to the extent such
|
||||
circumvention is effected by exercising rights under this License with
|
||||
respect to the covered work, and you intend that the waiver not restrict
|
||||
the operation or interaction of the work with technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, on any condition, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all notices
|
||||
stating that this License and the additional terms apply to the Program;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You must convey a modified version of the Program under terms that
|
||||
ensure that the construction of the modified version is licensed under
|
||||
this License. The modified version must be licensed under this License,
|
||||
and you must make the source code of the modified version available
|
||||
under the terms of this License. A modified version of the Program
|
||||
must be licensed under this License, not under any other license.
|
||||
Moreover, you must make it clear that the license of the modified
|
||||
version is this License, not any other license.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
If you convey a covered work in object code form, you must also
|
||||
convey the Corresponding Source under the terms of this License,
|
||||
together with information sufficient to allow the recipient to produce
|
||||
the Corresponding Source. This may be done in one of the following
|
||||
ways:
|
||||
|
||||
a) Accompany the object code with a written offer, valid for at
|
||||
least three years and valid for as long as you offer spare parts or
|
||||
customer support for that product, to give anyone who possesses the
|
||||
object code a complete copy of the Corresponding Source, under the
|
||||
terms of this License, plus a nominal fee for the cost of physically
|
||||
performing this transfer.
|
||||
|
||||
b) Accompany the object code with a written offer, valid for at
|
||||
least three years and valid for as long as you offer spare parts or
|
||||
customer support for that product, to provide a copy of the
|
||||
Corresponding Source from a public server or from a similar
|
||||
server that is chargeable.
|
||||
|
||||
c) Accompany the object code with a copy of the written offer
|
||||
given to the individual. This is only possible if you convey the
|
||||
object code in response to such an offer.
|
||||
|
||||
d) Convey the object code with a written offer that allows the
|
||||
recipient limited distribution of the object code and under terms
|
||||
that permit the recipient to reconstruct the Corresponding Source.
|
||||
|
||||
If the object code is conveyed in a tailed approach (i.e., with a
|
||||
written offer that is valid for at least three years and valid
|
||||
for as long as you offer spare parts or customer support for that
|
||||
product), the Corresponding Source must be provided under the terms
|
||||
of this License, plus a nominal fee for the cost of physically
|
||||
performing this transfer.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that apply to the entire Program shall be
|
||||
treated as if they were included in the License, to the extent that
|
||||
they are valid under applicable law. If additional permissions apply
|
||||
only to part of the Program, that part may be used separately under
|
||||
those permissions, but the entire Program remains governed by this
|
||||
License without regard to the additional terms.
|
||||
|
||||
Notwithstanding the above, no additional terms may be applied to
|
||||
the Program if they alter or restrict the meaning of this License.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the terms
|
||||
of this License).
|
||||
|
||||
However, if you cease all violation of a particular licensee, then
|
||||
their license from you under this License is reinstated (a)
|
||||
provisionally, unless and until the licensor explicitly and finally
|
||||
terminates your license, and (b) permanently, if the licensor fails to
|
||||
notify the violation by some reasonable means prior to 60 days after
|
||||
the notification.
|
||||
|
||||
Moreover, a license from you under this License is reinstated
|
||||
permanently if the licensee notifies you of the violation by some
|
||||
reasonable means, this is the first time you have received notice of
|
||||
this violation under this License, and the licensee cures the
|
||||
violation within 30 days after receiving the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you are not eligible to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a result of having interacted with the Program
|
||||
without any such propagation does not require acceptance. However,
|
||||
nothing else grants you permission to propagate or modify the Program.
|
||||
These actions infringe copyright if you do not have this License. If
|
||||
you do not accept this License then you may not modify the Program
|
||||
beyond the scope of fair use or other exceptions provided by
|
||||
copyright law.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for ensuring compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transfer of control of an organization,
|
||||
or substantially all assets of one, or a corporation, or a partnership,
|
||||
or an organization with or without a legal personality. If you convey
|
||||
a covered work to an entity transaction, and the entity received the
|
||||
covered work is granted a license for the work, then the license is
|
||||
also granted to the successor in interest that benefits from such a
|
||||
conveyance, but only if the successor agrees to the terms of this
|
||||
License.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of either the Program or a work on which the Program is based.
|
||||
|
||||
Each contributor retains the exclusive right, under their own
|
||||
patent law, to grant permission for making, using, selling, offering
|
||||
for sale, or importing the contribution, and to sublicense those
|
||||
rights at or through the contributor's own patent office.
|
||||
|
||||
If a patent holder conveys a covered work, and the work was
|
||||
included in the Program for the purpose of practicing the patent
|
||||
without a license, then the patent holder's license from you for
|
||||
the work is void, and the patent holder's patent license for the
|
||||
work will not be extended to the work by the terms of this License.
|
||||
|
||||
If a patent holder conveys a covered work as part of a larger
|
||||
fabrication, and the work was included in the Program for the
|
||||
purpose of practicing the patent without a license, then the patent
|
||||
holder's license from you for the work is void.
|
||||
|
||||
If a contributor that conveys a covered work is a patent licensee
|
||||
that extends the license to a larger fabrication, and the work was
|
||||
included in the Program for the purpose of practicing the patent
|
||||
without a license, then the license is void.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement
|
||||
or otherwise) that could require you to surrender the freedom granted
|
||||
under this License, you may not convey a covered work unless you also
|
||||
accept those conditions. For example, if you convey a covered work
|
||||
under terms that oblige you to convey the same freedom to others, you
|
||||
would be doing so only if you could also ensure the freedom of the
|
||||
recipients to receive and modify the work under this License.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a
|
||||
single combined program, and to convey the resulting work. The terms
|
||||
of this License will apply to the Program part of the work, and the
|
||||
terms of the GNU Affero General Public License will apply to the
|
||||
part of the work that is licensed under that license.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish new and/or revised versions
|
||||
of the GNU General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in
|
||||
detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions of either that numbered
|
||||
version or any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used for the
|
||||
Program, that proxy's public statement of acceptance of a version
|
||||
permanently authorizes that version for the use of the Program.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. OTHERWISE THE PROGRAM IS PROVIDED "AS IS" WITHOUT
|
||||
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
|
||||
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
|
||||
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICE, REPAIR OR
|
||||
CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW WILL ANY COPYRIGHT
|
||||
HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
|
||||
PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL,
|
||||
SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR
|
||||
INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA
|
||||
OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given effect, then the effect of those provisions is
|
||||
reduced to a minimum, and the Program shall be treated as subject to
|
||||
an implied warranty of merchantability and a right to damages for
|
||||
breach of contract or breach of the implied warranty of a
|
||||
merchantable quality.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be the best
|
||||
possible program, the best way to achieve this is to make it free
|
||||
software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do this, attach the following notices to your program. It is
|
||||
safest to attach them to the start of each source file to most
|
||||
effectively state the exclusion of warranty; and each file should
|
||||
have at least the "copyright" line and a pointer to where the full
|
||||
notice can be found.
|
||||
|
||||
tssr — Tiny Soccer Simulator Rust
|
||||
Copyright (C) 2026 Mark Robillard Jr (MARKMENTAL)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and physical mail.
|
||||
|
||||
If your program can perform simple terminal interactions, attach a
|
||||
short notice like this when the program starts:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w`.
|
||||
This is free software, and you are welcome to distribute it under
|
||||
certain conditions; type `show c` for details.
|
||||
|
||||
The hypothetical commands `show w` and `show c` should show the
|
||||
appropriate parts of the General Public License.
|
||||
|
||||
You should also get your employer (if you work as a student) or
|
||||
your school, if any, to sign a "copyright disclaimer" for the
|
||||
program, if necessary. For more information on how to do this, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your
|
||||
program into proprietary programs. If your program is a terminal
|
||||
emulator or a very simple text editor, the License does not permit
|
||||
incorporating it into a larger application. If you are building a
|
||||
tool to be used by the public, you might consider using the GNU
|
||||
Affero General Public License instead. For more information, see
|
||||
<https://www.gnu.org/licenses/licenses.html>.
|
||||
176
README.md
176
README.md
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
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.
|
||||
|
||||
## Author
|
||||
|
||||
Mark Robillard Jr (MARKMENTAL)
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
|
|
@ -13,22 +17,19 @@ 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
|
||||
./tssr # random match (Unicode table)
|
||||
./tssr --json # random match (JSON)
|
||||
./tssr --script match.tsrc # scripted match (table)
|
||||
./tssr --script match.tsrc --json # scripted match (JSON)
|
||||
./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);
|
||||
```
|
||||
|
||||
|
|
@ -37,9 +38,17 @@ chmod +x match.tsrc
|
|||
./match.tsrc
|
||||
```
|
||||
|
||||
## Output
|
||||
## Output Modes
|
||||
|
||||
### Table mode (default)
|
||||
| Mode | How to enable |
|
||||
|------|---------------|
|
||||
| Unicode table | Default (no flag, no `#define`) |
|
||||
| ASCII table | `#define ASCII_TABLE` in script |
|
||||
| JSON | `--json` CLI flag or `#define JSON` in script |
|
||||
|
||||
The `--json` CLI flag always takes priority over `#define`.
|
||||
|
||||
### Unicode table (default)
|
||||
|
||||
```
|
||||
╔════════════════════════════════════════╗
|
||||
|
|
@ -48,19 +57,33 @@ chmod +x match.tsrc
|
|||
║ Man Page City 2 - 1 Libre Pool║
|
||||
╠════════════════════════════════════════╣
|
||||
║ ║
|
||||
║ Shots 10 8 ║
|
||||
║ On Target 4 3 ║
|
||||
║ Possession 55% 45% ║
|
||||
║ 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`)
|
||||
### ASCII table (`#define ASCII_TABLE`)
|
||||
|
||||
```
|
||||
+--------------------------------------------+
|
||||
| TSSR MATCH RESULT |
|
||||
+--------------------------------------------+
|
||||
| Man Page City 2 - 1 Libre Pool |
|
||||
+--------------------------------------------+
|
||||
| |
|
||||
| Shots 10 8 |
|
||||
| On Target 4 3 |
|
||||
| Possession 55% 45% |
|
||||
+--------------------------------------------+
|
||||
```
|
||||
|
||||
### JSON (`--json` or `#define JSON`)
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -71,8 +94,7 @@ chmod +x match.tsrc
|
|||
"shots_on_target": 4,
|
||||
"possession": 55,
|
||||
"scorers": [
|
||||
{"player": "Marco Smith", "minute": 23},
|
||||
{"player": "Leo Brown", "minute": 67}
|
||||
{"player": "Marco Smith", "minute": 23}
|
||||
]
|
||||
},
|
||||
"away": {
|
||||
|
|
@ -88,88 +110,45 @@ chmod +x match.tsrc
|
|||
}
|
||||
```
|
||||
|
||||
## `.tsrc` Scripting Language
|
||||
## Scripting with `.tsrc`
|
||||
|
||||
`.tsrc` (Tiny Soccer Sim Rust C) is a minimal C-like DSL for defining teams, players, and match events programmatically.
|
||||
`.tsrc` (Tiny Soccer Sim Rust C) is a minimal C-like DSL for defining teams, players, goals, and output mode.
|
||||
|
||||
### Language Reference
|
||||
### Quick example
|
||||
|
||||
```tsrc
|
||||
// Declare a team variable
|
||||
string var_name = "Team Name";
|
||||
#!/usr/bin/env tssr
|
||||
#define ASCII_TABLE
|
||||
|
||||
// Assign a player to a team (by variable or literal)
|
||||
players["Player Name"] = var_name;
|
||||
players["Player Name"] = "Team Name";
|
||||
string home = "GNU United";
|
||||
string away = "BSD Athletic";
|
||||
|
||||
// Script a goal: minute (1-90), team (var or literal), player (literal)
|
||||
goal(45, var_name, "Player Name");
|
||||
goal(45, "Team Name", "Player Name");
|
||||
players["Richard Stallman"] = home;
|
||||
players["Brian Fox"] = home;
|
||||
players["Theo de Raadt"] = away;
|
||||
|
||||
// Select teams for the match (required)
|
||||
match(var_home, var_away);
|
||||
goal(12, home, "Richard Stallman");
|
||||
goal(34, away, "Theo de Raadt");
|
||||
goal(78, "BSD Athletic", "Theo de Raadt");
|
||||
|
||||
match(home, away);
|
||||
```
|
||||
|
||||
### Rules
|
||||
### Language syntax
|
||||
|
||||
- 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
|
||||
| Statement | Purpose |
|
||||
|-----------|---------|
|
||||
| `string var = "Team";` | Declare a team variable |
|
||||
| `players["Player"] = var;` | Assign player to team (variable or literal) |
|
||||
| `goal(min, team, "Player");` | Script a goal at a specific minute |
|
||||
| `match(home, away);` | Select teams for the match (required) |
|
||||
| `#define JSON / ASCII_TABLE / UNICODE_TABLE` | Set output mode |
|
||||
|
||||
### Validation Errors
|
||||
When `goal()` is used, the score becomes deterministic — goals, minutes, and scorers are exact. Shots are floored at the goal count. Without `goal()`, everything is fully randomized.
|
||||
|
||||
The parser is strict — it will fail with a clear message if:
|
||||
The parser is strict: any inconsistency produces a clear error and exits immediately.
|
||||
|
||||
- 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 |
|
||||
See [`docs/tsrc-language/README.md`](docs/tsrc-language/README.md) for the full language reference.
|
||||
|
||||
## Testing
|
||||
|
||||
|
|
@ -177,15 +156,26 @@ When no script is provided, teams are randomly selected from PES-style parodies
|
|||
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)
|
||||
19 integration tests covering:
|
||||
- Goal validation (player on team, minute range, duplicates, undefined variables)
|
||||
- Script validation (undefined variables, same team, missing match, no players, duplicate variables)
|
||||
- `#define` directive (all modes, duplicate, unknown)
|
||||
- Happy paths (valid scripts, variable references, full scripted match)
|
||||
|
||||
## Documentation
|
||||
|
||||
| Document | Description |
|
||||
|----------|-------------|
|
||||
| [`docs/tsrc-language/README.md`](docs/tsrc-language/README.md) | Full `.tsrc` language reference, grammar, validation rules |
|
||||
| [`docs/engine/README.md`](docs/engine/README.md) | Engine internals: random generation, scripted override, output formatting |
|
||||
|
||||
## 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
|
||||
- Release profile uses LTO, single codegen unit, panic=abort, symbol stripping
|
||||
- Sub-second cold compilation
|
||||
- Binary size: ~340KB stripped
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GNU General Public License v3.0 — see [LICENSE](LICENSE).
|
||||
|
|
|
|||
21
defines-test-match.tsrc
Executable file
21
defines-test-match.tsrc
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
#!/usr/bin/env tssr
|
||||
|
||||
#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, away, "Ken Thompson");
|
||||
goal(89, home, "Linus Torvalds");
|
||||
|
||||
match(home, away);
|
||||
280
docs/engine/README.md
Normal file
280
docs/engine/README.md
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
# tssr Engine Reference
|
||||
|
||||
The tssr engine generates soccer match results. It operates in two modes: **random** (everything fabricated on the spot) and **scripted** (driven by a `.tsr`c file).
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ CLI / .tsrc │────▶│ Match Gen │────▶│ Output │
|
||||
│ Input │ │ Engine │ │ Formatter │
|
||||
└──────────────┘ └──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
1. **Input** — CLI flags and/or `.tsrc` script parsed into `TsrcScript`
|
||||
2. **Match Generation** — `gen_match()` produces a `Match` struct
|
||||
3. **Output** — `print_table`, `print_ascii_table`, or `print_json` renders the result
|
||||
|
||||
## Random Mode
|
||||
|
||||
When no `.tsrc` script is provided, everything is randomized.
|
||||
|
||||
### Team Selection
|
||||
|
||||
Two distinct teams are picked from a hardcoded pool of 30 FOSS-parody names:
|
||||
|
||||
- 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
|
||||
|
||||
### Player Name Generation
|
||||
|
||||
Names are fabricated from two pools (48 first names, 50 last names):
|
||||
|
||||
```rust
|
||||
fn gen_name(rng) -> String {
|
||||
format!("{} {}", FIRST_NAMES.choose(rng), LAST_NAMES.choose(rng))
|
||||
}
|
||||
```
|
||||
|
||||
### Stat Ranges
|
||||
|
||||
| Stat | Range | Distribution |
|
||||
|------|-------|--------------|
|
||||
| Score (per team) | 0–5 | Uniform random |
|
||||
| Shots (per team) | 4–18 | Uniform random |
|
||||
| Shots on target | 1–min(shots, 10) | Uniform random |
|
||||
| Possession (home) | 38–62% | Uniform random |
|
||||
| Possession (away) | 100 - home | Derived |
|
||||
| Goal minute | 1–90 | Uniform random |
|
||||
|
||||
## Match Generation Flow
|
||||
|
||||
```
|
||||
gen_match(rng, script) -> Match
|
||||
```
|
||||
|
||||
### 1. Team Resolution
|
||||
|
||||
```rust
|
||||
let (home_name, away_name) = match script {
|
||||
Some(s) => (s.teams[home_var].clone(), s.teams[away_var].clone()),
|
||||
None => (random_team(), random_team_excluding(home)),
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Score & Scorers
|
||||
|
||||
Two paths depending on whether `goal()` was scripted:
|
||||
|
||||
**Scripted goals present:**
|
||||
- Partition goals by team
|
||||
- Score = count of goals per team
|
||||
- Scorers = exact `(player, minute)` from script
|
||||
|
||||
**No scripted goals:**
|
||||
- Score = `rng.gen_range(0..=5)` per team
|
||||
- Scorers = `pick_scorers(roster, score, rng)` — random selection from roster or name pools
|
||||
|
||||
### 3. Shots
|
||||
|
||||
**Scripted goals present** — shots floored at goal count:
|
||||
|
||||
```rust
|
||||
let shots = score.max(rng.gen_range(4..=18));
|
||||
let on_target = score.max(rng.gen_range(1..=shots.min(10)));
|
||||
```
|
||||
|
||||
**No scripted goals** — fully random:
|
||||
|
||||
```rust
|
||||
let shots = rng.gen_range(4..=18);
|
||||
let on_target = rng.gen_range(1..=shots.min(10));
|
||||
```
|
||||
|
||||
### 4. Possession
|
||||
|
||||
Always random: `rng.gen_range(38..=62)` for home, `100 - home` for away.
|
||||
|
||||
## Scripted Mode
|
||||
|
||||
When a `.tsrc` script is provided, parsed into `TsrcScript`:
|
||||
|
||||
```rust
|
||||
struct TsrcScript {
|
||||
teams: HashMap<String, String>, // var_name -> team_name
|
||||
roster: HashMap<String, Vec<String>>, // team_name -> [players]
|
||||
home_var: String, // variable name for home
|
||||
away_var: String, // variable name for away
|
||||
goals: Option<Vec<ScriptedGoal>>, // scripted goals (None = random)
|
||||
output_mode: OutputMode, // UnicodeTable, AsciiTable, Json
|
||||
}
|
||||
```
|
||||
|
||||
### Override Behavior
|
||||
|
||||
| Field | Source when scripted | Source when random |
|
||||
|-------|---------------------|-------------------|
|
||||
| Team names | `s.teams[home_var]` | Random from TEAM_NAMES |
|
||||
| Player roster | `s.roster[team]` | Empty (random names) |
|
||||
| Goal scorers | Exact from `s.goals` | `pick_scorers()` |
|
||||
| Score | Count from `s.goals` | `rng.gen_range(0..=5)` |
|
||||
| Shots | `max(score, random)` | `rng.gen_range(4..=18)` |
|
||||
| Possession | Always random | Always random |
|
||||
|
||||
## Output Formatting
|
||||
|
||||
Three output modes, selected by:
|
||||
1. `--json` CLI flag (highest priority)
|
||||
2. Script's `#define` directive
|
||||
3. Default: `UNICODE_TABLE`
|
||||
|
||||
### Unicode Table
|
||||
|
||||
Uses box-drawing characters:
|
||||
|
||||
```
|
||||
╔════════════════════════════════════════╗
|
||||
║ 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') ║
|
||||
╚════════════════════════════════════════╝
|
||||
```
|
||||
|
||||
### ASCII Table
|
||||
|
||||
Same layout, pure ASCII characters:
|
||||
|
||||
```
|
||||
+--------------------------------------------+
|
||||
| TSSR MATCH RESULT |
|
||||
+--------------------------------------------+
|
||||
| Man Page City 2 - 1 Libre Pool |
|
||||
+--------------------------------------------+
|
||||
+--------------------------------------------+
|
||||
| Goals: |
|
||||
| Man Page City: Marco Smith (23') |
|
||||
+--------------------------------------------+
|
||||
```
|
||||
|
||||
### 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}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Table Rendering Implementation
|
||||
|
||||
Both table modes share a single function parameterized by border characters:
|
||||
|
||||
```rust
|
||||
fn print_table_chars(m: &Match, horiz, vert, tl, tr, bl, br, cross)
|
||||
```
|
||||
|
||||
| Mode | horiz | vert | corners | cross |
|
||||
|------|-------|------|---------|-------|
|
||||
| Unicode | `═` | `║` | `╔╗╚╝` | `╠` |
|
||||
| ASCII | `-` | `\|` | `++++` | `+` |
|
||||
|
||||
Layout is content-driven — `inner_width` is calculated from the longest line (score, title, or goal text) plus 2 padding characters.
|
||||
|
||||
## CLI Argument Parsing
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
// --help / -h → print usage, exit
|
||||
// --json → JSON output
|
||||
// --script <file> → parse .tsrc
|
||||
// bare file path → shebang mode (auto-detect)
|
||||
}
|
||||
```
|
||||
|
||||
Shebang detection: if any non-flag argument is a path to an existing file, it's treated as a script.
|
||||
|
||||
## Build Configuration
|
||||
|
||||
```toml
|
||||
[profile.release]
|
||||
opt-level = 3 # Maximum optimization
|
||||
lto = true # Link-time optimization
|
||||
codegen-units = 1 # Single codegen unit for better optimization
|
||||
panic = "abort" # Smaller binary, no unwinding
|
||||
strip = true # Remove debug symbols
|
||||
```
|
||||
|
||||
## Project Layout
|
||||
|
||||
```
|
||||
tssr/
|
||||
├── Cargo.toml # Package config (lib + bin)
|
||||
├── src/
|
||||
│ ├── lib.rs # Library root (pub mod tsrc)
|
||||
│ ├── main.rs # CLI binary
|
||||
│ └── tsrc.rs # .tsrc parser + validation
|
||||
├── tests/
|
||||
│ └── tssr.rs # 19 integration tests
|
||||
├── docs/
|
||||
│ ├── tsrc-language/
|
||||
│ │ └── README.md # Language reference
|
||||
│ └── engine/
|
||||
│ └── README.md # Engine reference
|
||||
├── scripted_match.tsrc # Example: scripted goals
|
||||
├── defines-test-match.tsrc # Example: ASCII_TABLE mode
|
||||
├── gnu-bsd-soccer.tsrc # Example: variable references
|
||||
└── test_match.tsrc # Intentionally broken (for testing errors)
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Crate | Version | Purpose | Scope |
|
||||
|-------|---------|---------|-------|
|
||||
| `rand` | 0.8 | Random number generation | Runtime |
|
||||
| `tempfile` | 3 | Temporary files for tests | Dev only |
|
||||
|
||||
## Testing
|
||||
|
||||
```bash
|
||||
cargo test
|
||||
```
|
||||
|
||||
19 integration tests cover:
|
||||
- `.tsrc` language validation (13 tests)
|
||||
- `#define` directive handling (6 tests)
|
||||
- All error conditions produce correct error messages
|
||||
- Happy paths for valid scripts
|
||||
230
docs/tsrc-language/README.md
Normal file
230
docs/tsrc-language/README.md
Normal file
|
|
@ -0,0 +1,230 @@
|
|||
# `.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 `#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`.
|
||||
|
||||
## Complete Grammar
|
||||
|
||||
```
|
||||
script = statement*
|
||||
statement = define | string_decl | player_insert | goal_stmt | match_stmt
|
||||
define = "#define" identifier
|
||||
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 |
|
||||
|
||||
## Scripted Goals Behavior
|
||||
|
||||
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 |
|
||||
|
||||
## 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 `#define` in script |
|
||||
| `unknown #define 'FOO'` | Unrecognized output mode |
|
||||
|
||||
## 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') +
|
||||
+------------------------------------+
|
||||
```
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#!/home/mrkmntal/tssr/target/release/tssr
|
||||
#!/usr/bin/env tssr
|
||||
|
||||
string home = "GNU United";
|
||||
string away = "BSD Athletic";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/home/mrkmntal/tssr/target/release/tssr
|
||||
#!/usr/bin/env tssr
|
||||
|
||||
// GNU United vs BSD Athletic — fully scripted match
|
||||
|
||||
|
|
|
|||
31
src/main.rs
31
src/main.rs
|
|
@ -192,7 +192,7 @@ fn pad_line(content: &str, inner_width: usize) -> String {
|
|||
format!("{}{}", content, " ".repeat(inner_width.saturating_sub(len)))
|
||||
}
|
||||
|
||||
fn print_table(m: &Match) {
|
||||
fn print_table_chars(m: &Match, horiz: char, vert: char, tl: char, tr: char, bl: char, br: char, cross: char) {
|
||||
let home = &m.home.name;
|
||||
let away = &m.away.name;
|
||||
let score_line = format!("{home} {}-{} {away}", m.home.score, m.away.score);
|
||||
|
|
@ -230,15 +230,24 @@ fn print_table(m: &Match) {
|
|||
);
|
||||
let inner_width = max_content + 2;
|
||||
|
||||
println!("╔{}╗", "═".repeat(inner_width));
|
||||
println!("║{}║", pad_line(&format!("{:^w$}", title, w = inner_width), inner_width));
|
||||
println!("╠{}╣", "═".repeat(inner_width));
|
||||
println!("║{}║", pad_line(&format!("{:^w$}", score_line, w = inner_width), inner_width));
|
||||
println!("╠{}╣", "═".repeat(inner_width));
|
||||
let h = horiz.to_string().repeat(inner_width);
|
||||
println!("{}{}{}", tl, h, tr);
|
||||
println!("{}{}{}", vert, pad_line(&format!("{:^w$}", title, w = inner_width), inner_width), vert);
|
||||
println!("{}{}{}", cross, h, cross);
|
||||
println!("{}{}{}", vert, pad_line(&format!("{:^w$}", score_line, w = inner_width), inner_width), vert);
|
||||
println!("{}{}{}", cross, h, cross);
|
||||
for line in &lines {
|
||||
println!("║{}║", pad_line(line, inner_width));
|
||||
println!("{}{}{}", vert, pad_line(line, inner_width), vert);
|
||||
}
|
||||
println!("╚{}╝", "═".repeat(inner_width));
|
||||
println!("{}{}{}", bl, h, br);
|
||||
}
|
||||
|
||||
fn print_table(m: &Match) {
|
||||
print_table_chars(m, '═', '║', '╔', '╗', '╚', '╝', '╠');
|
||||
}
|
||||
|
||||
fn print_ascii_table(m: &Match) {
|
||||
print_table_chars(m, '-', '+', '+', '+', '+', '+', '+');
|
||||
}
|
||||
|
||||
fn print_json(m: &Match) {
|
||||
|
|
@ -337,6 +346,10 @@ fn main() {
|
|||
print_json(&m);
|
||||
}
|
||||
else {
|
||||
print_table(&m);
|
||||
match script.as_ref().map(|s| &s.output_mode) {
|
||||
Some(tsrc::OutputMode::Json) => print_json(&m),
|
||||
Some(tsrc::OutputMode::AsciiTable) => print_ascii_table(&m),
|
||||
_ => print_table(&m),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
33
src/tsrc.rs
33
src/tsrc.rs
|
|
@ -8,6 +8,13 @@ pub struct ScriptedGoal {
|
|||
pub player: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum OutputMode {
|
||||
UnicodeTable,
|
||||
AsciiTable,
|
||||
Json,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TsrcScript {
|
||||
pub teams: HashMap<String, String>,
|
||||
|
|
@ -15,6 +22,7 @@ pub struct TsrcScript {
|
|||
pub home_var: String,
|
||||
pub away_var: String,
|
||||
pub goals: Option<Vec<ScriptedGoal>>,
|
||||
pub output_mode: OutputMode,
|
||||
}
|
||||
|
||||
pub fn parse_tsrc(path: &str) -> Result<TsrcScript, String> {
|
||||
|
|
@ -31,6 +39,7 @@ pub fn parse_tsrc(path: &str) -> Result<TsrcScript, String> {
|
|||
let mut player_entries: HashMap<(String, String), usize> = HashMap::new();
|
||||
let mut goals: Vec<ScriptedGoal> = Vec::new();
|
||||
let mut goal_entries: HashMap<(u8, String), usize> = HashMap::new();
|
||||
let mut output_mode: OutputMode = OutputMode::UnicodeTable;
|
||||
|
||||
for (line_num, raw_line) in content.lines().enumerate() {
|
||||
let n = line_num + 1;
|
||||
|
|
@ -40,7 +49,16 @@ pub fn parse_tsrc(path: &str) -> Result<TsrcScript, String> {
|
|||
continue;
|
||||
}
|
||||
|
||||
if let Some(rest) = line.strip_prefix("string ") {
|
||||
if let Some(rest) = line.strip_prefix("#define ") {
|
||||
if output_mode != OutputMode::UnicodeTable {
|
||||
return Err(format!(
|
||||
"tsrc error: output mode already defined on line {} (duplicate #define)",
|
||||
n
|
||||
));
|
||||
}
|
||||
output_mode = parse_define(rest, n)?;
|
||||
}
|
||||
else if let Some(rest) = line.strip_prefix("string ") {
|
||||
parse_string_decl(rest, n, &mut teams, &mut var_lines)?;
|
||||
}
|
||||
else if let Some(rest) = line.strip_prefix("players") {
|
||||
|
|
@ -78,9 +96,22 @@ pub fn parse_tsrc(path: &str) -> Result<TsrcScript, String> {
|
|||
home_var,
|
||||
away_var,
|
||||
goals: if goals.is_empty() { None } else { Some(goals) },
|
||||
output_mode,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_define(s: &str, n: usize) -> Result<OutputMode, String> {
|
||||
match s.trim() {
|
||||
"JSON" => Ok(OutputMode::Json),
|
||||
"UNICODE_TABLE" => Ok(OutputMode::UnicodeTable),
|
||||
"ASCII_TABLE" => Ok(OutputMode::AsciiTable),
|
||||
other => Err(format!(
|
||||
"tsrc error: unknown #define '{}' on line {} (expected JSON, UNICODE_TABLE, or ASCII_TABLE)",
|
||||
other, n
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_script(
|
||||
teams: &HashMap<String, String>,
|
||||
roster: &HashMap<String, Vec<String>>,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
#!/home/mrkmntal/tssr/target/release/tssr
|
||||
#!/usr/bin/env tssr
|
||||
|
||||
// Man Page City vs Libre Pool
|
||||
string home = "Linux City";
|
||||
|
|
|
|||
|
|
@ -222,3 +222,90 @@ fn script_no_players_for_team() {
|
|||
let err = r.unwrap_err();
|
||||
assert!(err.contains("no players assigned"), "got: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn define_default_is_unicode_table() {
|
||||
let r = parse(r#"
|
||||
string h = "Team A";
|
||||
string a = "Team B";
|
||||
players["P1"] = h;
|
||||
players["P2"] = a;
|
||||
match(h, a);
|
||||
"#);
|
||||
assert!(r.is_ok(), "got: {:?}", r.err());
|
||||
assert_eq!(r.unwrap().output_mode, tssr::tsrc::OutputMode::UnicodeTable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn define_json() {
|
||||
let r = parse(r#"
|
||||
#define JSON
|
||||
string h = "Team A";
|
||||
string a = "Team B";
|
||||
players["P1"] = h;
|
||||
players["P2"] = a;
|
||||
match(h, a);
|
||||
"#);
|
||||
assert!(r.is_ok(), "got: {:?}", r.err());
|
||||
assert_eq!(r.unwrap().output_mode, tssr::tsrc::OutputMode::Json);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn define_ascii_table() {
|
||||
let r = parse(r#"
|
||||
#define ASCII_TABLE
|
||||
string h = "Team A";
|
||||
string a = "Team B";
|
||||
players["P1"] = h;
|
||||
players["P2"] = a;
|
||||
match(h, a);
|
||||
"#);
|
||||
assert!(r.is_ok(), "got: {:?}", r.err());
|
||||
assert_eq!(r.unwrap().output_mode, tssr::tsrc::OutputMode::AsciiTable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn define_unicode_table_explicit() {
|
||||
let r = parse(r#"
|
||||
#define UNICODE_TABLE
|
||||
string h = "Team A";
|
||||
string a = "Team B";
|
||||
players["P1"] = h;
|
||||
players["P2"] = a;
|
||||
match(h, a);
|
||||
"#);
|
||||
assert!(r.is_ok(), "got: {:?}", r.err());
|
||||
assert_eq!(r.unwrap().output_mode, tssr::tsrc::OutputMode::UnicodeTable);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn define_duplicate_error() {
|
||||
let r = parse(r#"
|
||||
#define JSON
|
||||
#define ASCII_TABLE
|
||||
string h = "Team A";
|
||||
string a = "Team B";
|
||||
players["P1"] = h;
|
||||
players["P2"] = a;
|
||||
match(h, a);
|
||||
"#);
|
||||
assert!(r.is_err());
|
||||
let err = r.unwrap_err();
|
||||
assert!(err.contains("duplicate #define"), "got: {}", err);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn define_unknown_error() {
|
||||
let r = parse(r#"
|
||||
#define FOO_BAR
|
||||
string h = "Team A";
|
||||
string a = "Team B";
|
||||
players["P1"] = h;
|
||||
players["P2"] = a;
|
||||
match(h, a);
|
||||
"#);
|
||||
assert!(r.is_err());
|
||||
let err = r.unwrap_err();
|
||||
assert!(err.contains("unknown #define"), "got: {}", err);
|
||||
assert!(err.contains("FOO_BAR"), "got: {}", err);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue