Initial Commit
This commit is contained in:
commit
f0855fc2b0
5 changed files with 498 additions and 0 deletions
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/target/
|
||||
*.rlib
|
||||
*.d
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
133
Cargo.lock
generated
Normal file
133
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tssr"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c17e8fafad82b542ff3717217ecdc736231b59e387768c9630123b4ce4d2db44"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.58"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "595f56e044df4f46a0c9a626f65c3d99eb8488f7e8a8baa12dd76326d9710bf2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
19
Cargo.toml
Normal file
19
Cargo.toml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "tssr"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Tiny Soccer Simulator Rust - generates random soccer matches on the spot"
|
||||
|
||||
[[bin]]
|
||||
name = "tssr"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8"
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
strip = true
|
||||
93
README.md
Normal file
93
README.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# tssr — Tiny Soccer Simulator Rust
|
||||
|
||||
A blazing-fast CLI that generates completely random, fabricated soccer matches on the spot. Run it, get a match. That's it.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
The binary will be at `target/release/tssr`.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
./tssr # Print a unicode table match report
|
||||
./tssr --json # Print the same match as JSON
|
||||
```
|
||||
|
||||
## 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}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Team Names
|
||||
|
||||
All team names are 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
|
||||
|
||||
## Performance
|
||||
|
||||
- Single dependency (`rand`)
|
||||
- Release profile uses LTO, single codegen unit, panic=abort, and symbol stripping
|
||||
- Designed for sub-second cold compilation and instant execution
|
||||
244
src/main.rs
Normal file
244
src/main.rs
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
use rand::seq::SliceRandom;
|
||||
use rand::Rng;
|
||||
use std::env;
|
||||
|
||||
const TEAM_NAMES: &[&str] = &[
|
||||
"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",
|
||||
];
|
||||
|
||||
const FIRST_NAMES: &[&str] = &[
|
||||
"Marco", "Luis", "Carlos", "Pedro", "James", "John", "Tom", "Alex",
|
||||
"Sam", "Dan", "Mike", "Ben", "Jack", "Luke", "Max", "Leo",
|
||||
"Kai", "Rio", "Zion", "Finn", "Ade", "Noah", "Evan", "Oscar",
|
||||
"Hugo", "Iker", "Pau", "Dani", "Ivan", "Raul", "Diego", "Bruno",
|
||||
"Felix", "Niklas", "Leroy", "Florin", "Jamal", "Mason", "Phil",
|
||||
"David", "Paul", "Mark", "Luke", "John", "Simon", "Adam", "Aaron",
|
||||
];
|
||||
|
||||
const LAST_NAMES: &[&str] = &[
|
||||
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller",
|
||||
"Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez",
|
||||
"Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin",
|
||||
"Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark",
|
||||
"Ramirez", "Lewis", "Robinson", "Walker", "Young", "Allen", "King",
|
||||
"Wright", "Scott", "Torres", "Nguyen", "Hill", "Flores", "Green",
|
||||
"Adams", "Nelson", "Baker", "Hall", "Rivera", "Campbell", "Mitchell",
|
||||
"Carter", "Roberts",
|
||||
];
|
||||
|
||||
struct Team {
|
||||
name: &'static str,
|
||||
score: u8,
|
||||
shots: u8,
|
||||
shots_on_target: u8,
|
||||
possession: u8,
|
||||
scorers: Vec<(String, u8)>,
|
||||
}
|
||||
|
||||
struct Match {
|
||||
home: Team,
|
||||
away: Team,
|
||||
}
|
||||
|
||||
fn gen_name(rng: &mut rand::rngs::ThreadRng) -> String {
|
||||
let first = FIRST_NAMES.choose(rng).unwrap();
|
||||
let last = LAST_NAMES.choose(rng).unwrap();
|
||||
format!("{} {}", first, last)
|
||||
}
|
||||
|
||||
fn gen_match(rng: &mut rand::rngs::ThreadRng) -> Match {
|
||||
let idx1 = rng.gen_range(0..TEAM_NAMES.len());
|
||||
let mut idx2 = rng.gen_range(0..TEAM_NAMES.len());
|
||||
while idx2 == idx1 {
|
||||
idx2 = rng.gen_range(0..TEAM_NAMES.len());
|
||||
}
|
||||
let home_name = TEAM_NAMES[idx1];
|
||||
let away_name = TEAM_NAMES[idx2];
|
||||
|
||||
let home_score = rng.gen_range(0..=5u8);
|
||||
let away_score = rng.gen_range(0..=5u8);
|
||||
let possession = rng.gen_range(38..=62u8);
|
||||
|
||||
let home_shots = rng.gen_range(4..=18u8);
|
||||
let away_shots = rng.gen_range(4..=18u8);
|
||||
let home_on_target = rng.gen_range(1..=home_shots.min(10));
|
||||
let away_on_target = rng.gen_range(1..=away_shots.min(10));
|
||||
|
||||
let home_scorers: Vec<(String, u8)> = (0..home_score)
|
||||
.map(|_| (gen_name(rng), rng.gen_range(1..=90u8)))
|
||||
.collect();
|
||||
|
||||
let away_scorers: Vec<(String, u8)> = (0..away_score)
|
||||
.map(|_| (gen_name(rng), rng.gen_range(1..=90u8)))
|
||||
.collect();
|
||||
|
||||
Match {
|
||||
home: Team {
|
||||
name: home_name,
|
||||
score: home_score,
|
||||
shots: home_shots,
|
||||
shots_on_target: home_on_target,
|
||||
possession,
|
||||
scorers: home_scorers,
|
||||
},
|
||||
away: Team {
|
||||
name: away_name,
|
||||
score: away_score,
|
||||
shots: away_shots,
|
||||
shots_on_target: away_on_target,
|
||||
possession: 100 - possession,
|
||||
scorers: away_scorers,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn pad_line(content: &str, inner_width: usize) -> String {
|
||||
let len = content.chars().count();
|
||||
format!("{}{}", content, " ".repeat(inner_width.saturating_sub(len)))
|
||||
}
|
||||
|
||||
fn print_table(m: &Match) {
|
||||
let home = m.home.name;
|
||||
let away = m.away.name;
|
||||
let score_line = format!("{home} {}-{} {away}", m.home.score, m.away.score);
|
||||
|
||||
let shots_col = format!("{:>4} {:>4}", m.home.shots, m.away.shots);
|
||||
let ontarget_col = format!("{:>4} {:>4}", m.home.shots_on_target, m.away.shots_on_target);
|
||||
let possession_col = format!("{:>3}% {:>3}%", m.home.possession, m.away.possession);
|
||||
|
||||
let stat_rows: Vec<String> = vec![
|
||||
format!("{:<12}{}", "Shots", shots_col),
|
||||
format!("{:<12}{}", "On Target", ontarget_col),
|
||||
format!("{:<12}{}", "Possession", possession_col),
|
||||
];
|
||||
|
||||
let mut lines: Vec<String> = Vec::new();
|
||||
lines.push(String::new());
|
||||
lines.extend(stat_rows);
|
||||
lines.push(String::new());
|
||||
lines.push("Goals:".to_string());
|
||||
if m.home.scorers.is_empty() && m.away.scorers.is_empty() {
|
||||
lines.push("No goals scored.".to_string());
|
||||
} else {
|
||||
for (name, min) in &m.home.scorers {
|
||||
lines.push(format!("{home}: {name} ({min}')"));
|
||||
}
|
||||
for (name, min) in &m.away.scorers {
|
||||
lines.push(format!("{away}: {name} ({min}')"));
|
||||
}
|
||||
}
|
||||
|
||||
let title = "TSSR MATCH RESULT";
|
||||
let max_content = score_line.len().max(title.len()).max(
|
||||
lines.iter().map(|l| l.chars().count()).max().unwrap_or(0)
|
||||
);
|
||||
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));
|
||||
for line in &lines {
|
||||
println!("║{}║", pad_line(line, inner_width));
|
||||
}
|
||||
println!("╚{}╝", "═".repeat(inner_width));
|
||||
}
|
||||
|
||||
fn print_json(m: &Match) {
|
||||
fn scorers_json(scorers: &[(String, u8)]) -> String {
|
||||
let entries: Vec<String> = scorers
|
||||
.iter()
|
||||
.map(|(n, m)| format!(r#"{{"player":"{}","minute":{}}}"#, n, m))
|
||||
.collect();
|
||||
format!("[{}]", entries.join(","))
|
||||
}
|
||||
|
||||
println!(
|
||||
r#"{{
|
||||
"home": {{
|
||||
"team": "{}",
|
||||
"score": {},
|
||||
"shots": {},
|
||||
"shots_on_target": {},
|
||||
"possession": {},
|
||||
"scorers": {}
|
||||
}},
|
||||
"away": {{
|
||||
"team": "{}",
|
||||
"score": {},
|
||||
"shots": {},
|
||||
"shots_on_target": {},
|
||||
"possession": {},
|
||||
"scorers": {}
|
||||
}}
|
||||
}}"#,
|
||||
m.home.name,
|
||||
m.home.score,
|
||||
m.home.shots,
|
||||
m.home.shots_on_target,
|
||||
m.home.possession,
|
||||
scorers_json(&m.home.scorers),
|
||||
m.away.name,
|
||||
m.away.score,
|
||||
m.away.shots,
|
||||
m.away.shots_on_target,
|
||||
m.away.possession,
|
||||
scorers_json(&m.away.scorers),
|
||||
);
|
||||
}
|
||||
|
||||
fn print_usage() {
|
||||
println!("Usage: tssr [--json] [--help]");
|
||||
println!();
|
||||
println!("Options:");
|
||||
println!(" --json Output match as JSON");
|
||||
println!(" -h, --help Show this help message");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.iter().any(|a| a == "--help" || a == "-h") {
|
||||
print_usage();
|
||||
return;
|
||||
}
|
||||
let json_mode = args.iter().any(|a| a == "--json");
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let m = gen_match(&mut rng);
|
||||
|
||||
if json_mode {
|
||||
print_json(&m);
|
||||
} else {
|
||||
print_table(&m);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue