Skip to content

Commit 86f97ee

Browse files
committed
final tAAAAAAAAAsks before release
1 parent 464c913 commit 86f97ee

File tree

5 files changed

+264
-1
lines changed

5 files changed

+264
-1
lines changed

.github/FUNDING.yml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# These are supported funding model platforms
2+
3+
github: ovnanova
4+
ko_fi: ovnanova
5+
liberapay: ovnanova

.github/workflows/rust.yml

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Rust
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
env:
10+
CARGO_TERM_COLOR: always
11+
12+
jobs:
13+
build:
14+
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
- name: Build
20+
run: cargo build --verbose
21+
- name: Run tests
22+
run: cargo test --verbose

Cargo.toml

+11
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,14 @@ edition = "2021"
66
[dependencies]
77
crossterm = "0.28.1"
88
rand = "0.8.5"
9+
10+
[dev-dependencies]
11+
unicode-segmentation = "1.12.0"
12+
13+
[profile.release]
14+
opt-level = 3
15+
lto = true
16+
codegen-units = 1
17+
panic = 'abort'
18+
strip = true
19+
debug = false

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
# AAAAAAAAA
22

3-
A̴̖͚̭̟ͤ̀A͕̬͍̓͟҉͉Ą͖̜͍̭͗̕A̘͇͓͔ͫ̕͝Ä̷̲̯̗̩́͠Ḁ̸̸̬͎͚̍À̷̼̟̖͉̈́A̛̳̻̬̘ͤ͜A̧͍̲̠͕͊̕A̹͏̣̩̞ͦ͘A̸̪̤̤̘ͮ̕A̤͉̤͌́͡ͅÀ̤͍̘̜ͬ͡A̧̨͎͉̬̝ͩA̖̣̟͓ͯ͢͡A̴̻̗͕̥̅̕A̻̗̫̹͋͢͟Ä̛̘̬̺͈̕A̛̺̬̪̘ͤ͞A̶̗̹̝̳ͭ̕À̸͚̙̫̬̉Ą̸̤̖̺ͩͅA̧͕͉̫̤͗͡Ā̧̭̰̤̙͞A̡͙͈̱͙ͩ͝A̴͕̗̭̱̍̀A̼̜̖̰̐͟͞Ą̱͔͇̳̋̕A̖͉͚̬͑͞͝À̷̮͎͍͍́A̢̡̟͙̯̾ͅÁ̵̝̫̭͕̓A̢̲̦̟ͨ͏̳A͉͏̯̯̻ͫ͟Ā̛̰̞͕͡ͅẠ͏̯͉̲̊͞Ȁ̸̬͖͙͈͝Å̢̤͕̠̦͘A̹҉͎̜̦́͟A͕̬̟̤͆͟͞A̴͙͎͕̹ͫ͡A̖̗̬ͪ͝͏̬A̶͙̻̣̱͐͢Ą̦̮͙̰̀̃A̠͏̫̦̠̈́͟A̧̛̩̹͈͇̚A̡̬͉̝̭͒́Ä̷̸͚̹̥ͅẢ̸̛͖̼̗̼A̸̢̺̣͙̱ͤÁ̶̼̝̠̠̏A̬͙͈͇͗͘͝A̵̯͍̹̬͌͡A͙̯̯̓͘͏̣Ạ̸̼̪͓́̽A̙̤̠̫ͥ͡͠A̴̢͍̝̮̭̾Ą͈̳̗̼̀͞Ä̴̛͍̠̘́ͅA͙͚̝̗ͪ͢͞A͍̳̰͓̒̕͝Ǎ̸̡͇̯͎͇A̢̧̮͖͚͕̓Ą̧̭͎͕̳͌Å͚͖͉̬͘͜A̸̧͎͙̘̞̿Á̙̹̬̱̒̕A̙҉͈̠͉͋͝Ạ̰̱͖͛̕͜Ạ̶̭̗͔ͩ͠A̛̰̠̟͔̓͝A̯̳̠̣̅͜͜A̧̢̲̮̰͎̒A̷̙̫̫͔̒́A̵̹̬̗̟ͬ͡A͔̹̙͓ͬ̕͞A̛͉͓͕̫͂͢À̛͉̬̰͔̿A̜̳͈̬ͯ͡͝Å̸̻̣̥҉͍Ă̴̳̜̘͠ͅA̻̯͖ͮ͘͢ͅǍ̛̮̬ͅ͏̳Ą̛̞̥̫̟̂A̷͕̫̥̱͊͞Á͔̻͔͍̌͜A̶̯̩͎ͨ͜ͅÄ̧͉̘͚͖̀Ȧ͔̜̯͖́͘A̜̰̺̦ͬ́͝A̲̤͕̖͑́̕A̸̡̪̖̼ͥͅA̷̫͎̠̟ͪ͝Ạ̴̤͈̱̒̀Ă̡̩̙̲̤͞Á̸̳͖̤̞͞Ã̫̪͖͈̕͠A̵̳̬͉̿͝ͅÀ̖̘̪̾͠ͅĄ͍̩̣̀̂ͅA͙͏̵̘͚̻̓À̛̞̯̫̙͝A̳͚̪̦ͬ͠͡A̳̪͙̟̚͟͡A̡̭̬̬̟̅͢A̶̛̜̦̯ͣͅA̸̩͉̺͔ͪ͡A̼̙͖̰ͥ́͠Å̢̮͖̭̟͢A͈̟̲͛͜͟ͅÂ̪̣͎̣͘͢A͓̞͉̤̾͠͡A̧̗̘̖̲͂̕Ȧ̡͕̼̮̙͜Ȃ̶̦͚̞̫͢A̢̲͙̰͎ͦ́Ä̴̢͇̯̤̫Á̸͎̠͖̹͞Á͚̹͈̠̎́À̡̧̜͙͇̙Ą̛͕̲̲̬ͦĄ̹̻̥̖̊͠A͖̭̯̱̚͢͜A̴̜͍̫̐͏̜A̤̼̱͉͛͘͢A̞̫̠̯ͥ̀͢A̡̱͎̻͈̒͟Ä̶͈̟̹̤̀A͙͏̻̩̰̿͡A̦̖̼͍ͪ̕͡A̬͇͚͕ͤ͢͝A̸̧̩̠͔͈͊A͎҉̛͇̣͓̇Ą͓͓̖͋͜ͅA̧͇̱̰̱̔͡A̧̹̬͍̜̎͠Á͍͉̤̭̀͘A̧̪͖̠̼͊̀Ȃ̷̟̞̹͔͞A̴̫̥̫͛̀ͅĄ̝̞̙͎̄͢À͕̖͍̻͜͢A̷̢̻̞͕̰̾A̛͉͈̝ͨ́ͅÅ̗͚͙̲͟͝Ą͕̗̻̣͒͘À͙̼̲̥ͤ͜Ả͔̘̻̮͜͡Ả̴̞̳͕̫̕Ä̵̹͇͎̯͘A̳̱͓̼ͬ́̕A̠҉̴͇̥̤̀Ă̴̳̲̮̜͝A̶͔͇̻͙̒͝À̼̖̭̤ͬ͢Ḁ̛̬̩̏͘ͅA̷̛̱̣̼̯ͣÂ̛̹̗̭̱͠A̡͚͚̠̠ͪ͢A̺̰̖̠̓͝͡A̱̫̲̹̅̀͜A̧͖͙͓̼ͤ͢A̵̱͈̙̯͌͠A̩̮̫̻ͣ͘͝A̛̺̦̥͔̓͘A̵̖̟͉ͦ҉̣Ä̧̞̞͕̮͜A̻̮̪̲̅͞͡A̧̛̱̣̭̝̾A̦͏̵͔͖̘ͣÁ̷̯̮̞͈ͥA̘͍̒͢ͅ͏̠A̴̛̘̹̙͔͗A̢̨͔̝̺͙ͯA̖͏̺̜̫͋͡Å̸̩̟̞̖͘A̹̞͚̱̎͡͝A̷̸̩̤͍͈ͥȂ͙̭͎͘͡ͅA̹̱̠̣̔́͠A̵̡̝̤͇ͩͅḀ̧̞̦̣́̅Ą̛͚̖̬̫̈́Ǎ̡̧̠̘̪͈A̳͇̰̳ͤ͜͝Ã̰̲͉̦̕͝A̢̨̗̻̲̭̋A̸̧͚̟̖̮͋A̶̠͈̘ͯ͡ͅ
3+
A̴̖͚̭̟ͤ̀A͕̬͍̓͟҉͉Ą͖̜͍̭͗̕A̘͇͓͔ͫ̕͝Ä̷̲̯̗̩́͠Ḁ̸̸̬͎͚̍À̷̼̟̖͉̈́A̛̳̻̬̘ͤ͜A̧͍̲̠͕͊̕A̹͏̣̩̞ͦ͘A̸̪̤̤̘ͮ̕A̤͉̤͌́͡ͅÀ̤͍̘̜ͬ͡A̧̨͎͉̬̝ͩA̖̣̟͓ͯ͢͡A̴̻̗͕̥̅̕A̻̗̫̹͋͢͟Ä̛̘̬̺͈̕A̛̺̬̪̘ͤ͞A̶̗̹̝̳ͭ̕À̸͚̙̫̬̉Ą̸̤̖̺ͩͅA̧͕͉̫̤͗͡Ā̧̭̰̤̙͞A̡͙͈̱͙ͩ͝A̴͕̗̭̱̍̀A̼̜̖̰̐͟͞Ą̱͔͇̳̋̕A̖͉͚̬͑͞͝À̷̮͎͍͍́A̢̡̟͙̯̾ͅÁ̵̝̫̭͕̓A̢̲̦̟ͨ͏̳A͉͏̯̯̻ͫ͟Ā̛̰̞͕͡ͅẠ͏̯͉̲̊͞Ȁ̸̬͖͙͈͝Å̢̤͕̠̦͘A̹҉͎̜̦́͟A͕̬̟̤͆͟͞A̴͙͎͕̹ͫ͡A̖̗̬ͪ͝͏̬A̶͙̻̣̱͐͢Ą̦̮͙̰̀̃A̠͏̫̦̠̈́͟A̧̛̩̹͈͇̚A̡̬͉̝̭͒́Ä̷̸͚̹̥ͅẢ̸̛͖̼̗̼A̸̢̺̣͙̱ͤÁ̶̼̝̠̠̏A̬͙͈͇͗͘͝A̵̯͍̹̬͌͡A͙̯̯̓͘͏̣Ạ̸̼̪͓́̽A̙̤̠̫ͥ͡͠A̴̢͍̝̮̭̾Ą͈̳̗̼̀͞Ä̴̛͍̠̘́ͅA͙͚̝̗ͪ͢͞A͍̳̰͓̒̕͝Ǎ̸̡͇̯͎͇A̢̧̮͖͚͕̓Ą̧̭͎͕̳͌Å͚͖͉̬͘͜A̸̧͎͙̘̞̿Á̙̹̬̱̒̕A̙҉͈̠͉͋͝Ạ̰̱͖͛̕͜Ạ̶̭̗͔ͩ͠A̛̰̠̟͔̓͝A̯̳̠̣̅͜͜A̧̢̲̮̰͎̒A̷̙̫̫͔̒́A̵̹̬̗̟ͬ͡A͔̹̙͓ͬ̕͞A̛͉͓͕̫͂͢À̛͉̬̰͔̿A̜̳͈̬ͯ͡͝Å̸̻̣̥҉͍Ă̴̳̜̘͠ͅA̻̯͖ͮ͘͢ͅǍ̛̮̬ͅ͏̳Ą̛̞̥̫̟̂A̷͕̫̥̱͊͞Á͔̻͔͍̌͜A̶̯̩͎ͨ͜ͅÄ̧͉̘͚͖̀Ȧ͔̜̯͖́͘A̜̰̺̦ͬ́͝A̲̤͕̖͑́̕A̸̡̪̖̼ͥͅA̷̫͎̠̟ͪ͝Ạ̴̤͈̱̒̀Ă̡̩̙̲̤͞Á̸̳͖̤̞͞Ã̫̪͖͈̕͠A̵̳̬͉̿͝ͅÀ̖̘̪̾͠ͅĄ͍̩̣̀̂ͅA͙͏̵̘͚̻̓À̛̞̯̫̙͝A̳͚̪̦ͬ͠͡A̳̪͙̟̚͟͡A̡̭̬̬̟̅͢A̶̛̜̦̯ͣͅA̸̩͉̺͔ͪ͡A̼̙͖̰ͥ́͠Å̢̮͖̭̟͢A͈̟̲͛͜͟ͅÂ̪̣͎̣͘͢A͓̞͉̤̾͠͡A̧̗̘̖̲͂̕Ȧ̡͕̼̮̙͜Ȃ̶̦͚̞̫͢A̢̲͙̰͎ͦ́Ä̴̢͇̯̤̫Á̸͎̠͖̹͞Á͚̹͈̠̎́À̡̧̜͙͇̙Ą̛͕̲̲̬ͦĄ̹̻̥̖̊͠A͖̭̯̱̚͢͜A̴̜͍̫̐͏̜A̤̼̱͉͛͘͢A̞̫̠̯ͥ̀͢A̡̱͎̻͈̒͟Ä̶͈̟̹̤̀A͙͏̻̩̰̿͡A̦̖̼͍ͪ̕͡A̬͇͚͕ͤ͢͝A̸̧̩̠͔͈͊A͎҉̛͇̣͓̇Ą͓͓̖͋͜ͅA̧͇̱̰̱̔͡A̧̹̬͍̜̎͠Á͍͉̤̭̀͘A̧̪͖̠̼͊̀Ȃ̷̟̞̹͔͞A̴̫̥̫͛̀ͅĄ̝̞̙͎̄͢À͕̖͍̻͜͢A̷̢̻̞͕̰̾A̛͉͈̝ͨ́ͅÅ̗͚͙̲͟͝Ą͕̗̻̣͒͘À͙̼̲̥ͤ͜Ả͔̘̻̮͜͡Ả̴̞̳͕̫̕Ä̵̹͇͎̯͘A̳̱͓̼ͬ́̕A̠҉̴͇̥̤̀Ă̴̳̲̮̜͝A̶͔͇̻͙̒͝À̼̖̭̤ͬ͢Ḁ̛̬̩̏͘ͅA̴̖͚̭̟ͤ̀A͕̬͍̓͟҉͉Ą͖̜͍̭͗̕A̘͇͓͔ͫ̕͝Ä̷̲̯̗̩́͠Ḁ̸̸̬͎͚̍À̷̼̟̖͉̈́A̛̳̻̬̘ͤ͜A̧͍̲̠͕͊̕A̹͏̣̩̞ͦ͘A̸̪̤̤̘ͮ̕A̤͉̤͌́͡ͅÀ̤͍̘̜ͬ͡A̧̨͎͉̬̝ͩA̖̣̟͓ͯ͢͡A̴̻̗͕̥̅̕A̻̗̫̹͋͢͟Ä̛̘̬̺͈̕A̛̺̬̪̘ͤ͞A̶̗̹̝̳ͭ̕À̸͚̙̫̬̉Ą̸̤̖̺ͩͅA̧͕͉̫̤͗͡Ā̧̭̰̤̙͞A̡͙͈̱͙ͩ͝A̴͕̗̭̱̍̀A̼̜̖̰̐͟͞Ą̱͔͇̳̋̕A̖͉͚̬͑͞͝À̷̮͎͍͍́A̢̡̟͙̯̾ͅÁ̵̝̫̭͕̓A̢̲̦̟ͨ͏̳A͉͏̯̯̻ͫ͟Ā̛̰̞͕͡ͅẠ͏̯͉̲̊͞Ȁ̸̬͖͙͈͝Å̢̤͕̠̦͘A̹҉͎̜̦́͟A͕̬̟̤͆͟͞A̴͙͎͕̹ͫ͡A̖̗̬ͪ͝͏̬A̶͙̻̣̱͐͢Ą̦̮͙̰̀̃A̠͏̫̦̠̈́͟A̧̛̩̹͈͇̚A̡̬͉̝̭͒́Ä̷̸͚̹̥ͅẢ̸̛͖̼̗̼A̸̢̺̣͙̱ͤÁ̶̼̝̠̠̏A̬͙͈͇͗͘͝A̵̯͍̹̬͌͡A͙̯̯̓͘͏̣Ạ̸̼̪͓́̽A̙̤̠̫ͥ͡͠A̴̢͍̝̮̭̾Ą͈̳̗̼̀͞Ä̴̛͍̠̘́ͅA͙͚̝̗ͪ͢͞A͍̳̰͓̒̕͝Ǎ̸̡͇̯͎͇A̢̧̮͖͚͕̓Ą̧̭͎͕̳͌Å͚͖͉̬͘͜A̸̧͎͙̘̞̿Á̙̹̬̱̒̕A̙҉͈̠͉͋͝Ạ̰̱͖͛̕͜Ạ̶̭̗͔ͩ͠A̛̰̠̟͔̓͝A̯̳̠̣̅͜͜A̧̢̲̮̰͎̒A̷̙̫̫͔̒́A̵̹̬̗̟ͬ͡A͔̹̙͓ͬ̕͞A̛͉͓͕̫͂͢À̛͉̬̰͔̿A̜̳͈̬ͯ͡͝Å̸̻̣̥҉͍Ă̴̳̜̘͠ͅA̻̯͖ͮ͘͢ͅǍ̛̮̬ͅ͏̳Ą̛̞̥̫̟̂A̷͕̫̥̱͊͞Á͔̻͔͍̌͜A̶̯̩͎ͨ͜ͅÄ̧͉̘͚͖̀Ȧ͔̜̯͖́͘A̜̰̺̦ͬ́͝A̲̤͕̖͑́̕A̸̡̪̖̼ͥͅA̷̫͎̠̟ͪ͝Ạ̴̤͈̱̒̀Ă̡̩̙̲̤͞Á̸̳͖̤̞͞Ã̫̪͖͈̕͠A̵̳̬͉̿͝ͅÀ̖̘̪̾͠ͅĄ͍̩̣̀̂ͅA͙͏̵̘͚̻̓À̛̞̯̫̙͝A̳͚̪̦ͬ͠͡A̳̪͙̟̚͟͡A̡̭̬̬̟̅͢A̶̛̜̦̯ͣͅA̸̩͉̺͔ͪ͡A̼̙͖̰ͥ́͠Å̢̮͖̭̟͢A͈̟̲͛͜͟ͅÂ̪̣͎̣͘͢A͓̞͉̤̾͠͡A̧̗̘̖̲͂̕Ȧ̡͕̼̮̙͜Ȃ̶̦͚̞̫͢A̢̲͙̰͎ͦ́Ä̴̢͇̯̤̫Á̸͎̠͖̹͞Á͚̹͈̠̎́À̡̧̜͙͇̙Ą̛͕̲̲̬ͦĄ̹̻̥̖̊͠A͖̭̯̱̚͢͜A̴̜͍̫̐͏̜A̤̼̱͉͛͘͢A̞̫̠̯ͥ̀͢A̡̱͎̻͈̒͟Ä̶͈̟̹̤̀A͙͏̻̩̰̿͡A̦̖̼͍ͪ̕͡A̬͇͚͕ͤ͢͝A̸̧̩̠͔͈͊A͎҉̛͇̣͓̇Ą͓͓̖͋͜ͅA̧͇̱̰̱̔͡A̧̹̬͍̜̎͠Á͍͉̤̭̀͘A̧̪͖̠̼͊̀Ȃ̷̟̞̹͔͞A̴̫̥̫͛̀ͅĄ̝̞̙͎̄͢À͕̖͍̻͜͢A̷̢̻̞͕̰̾A̛͉͈̝ͨ́ͅÅ̗͚͙̲͟͝Ą͕̗̻̣͒͘À͙̼̲̥ͤ͜Ả͔̘̻̮͜͡Ả̴̞̳͕̫̕Ä̵̹͇͎̯͘A̳̱͓̼ͬ́̕A̠҉̴͇̥̤̀Ă̴̳̲̮̜͝A̶͔͇̻͙̒͝À̼̖̭̤ͬ͢Ḁ̛̬̩̏͘ͅ
4+
5+
[AAAAAAAAA.webm](https://github.com/user-attachments/assets/4c710cad-0067-4a95-8e51-7fab017c4bc1)

src/main.rs

+223
Original file line numberDiff line numberDiff line change
@@ -252,3 +252,226 @@ fn main() -> io::Result<()> {
252252

253253
Ok(())
254254
}
255+
256+
#[cfg(test)]
257+
mod tests {
258+
use super::*;
259+
use std::collections::HashSet;
260+
use unicode_segmentation::UnicodeSegmentation;
261+
262+
#[test]
263+
fn test_random_string_length() {
264+
for _ in 0..1000000 {
265+
let s = random_string();
266+
let grapheme_count = s.graphemes(true).count();
267+
assert!(
268+
(1..=32).contains(&grapheme_count),
269+
"Grapheme count out of bounds: {} (string: {})",
270+
grapheme_count,
271+
s
272+
);
273+
}
274+
}
275+
276+
#[test]
277+
fn test_all_chars_appear() {
278+
let mut appearances = HashSet::<&'static str>::new();
279+
for _ in 0..10000 {
280+
let s = random_string();
281+
appearances.extend(CHAR_SET.iter().filter(|&&c| s.contains(c)));
282+
}
283+
assert_eq!(
284+
appearances.len(),
285+
CHAR_SET.len(),
286+
"Not all characters appeared in 10000 iterations"
287+
);
288+
}
289+
290+
#[test]
291+
fn test_stream_direction_changes() {
292+
let mut stream = Stream::new(80, 24);
293+
let mut direction_changes = 0;
294+
let mut last_direction = stream.direction.get_offset();
295+
296+
for _ in 0..1000 {
297+
stream.update(80, 24);
298+
let new_direction = stream.direction.get_offset();
299+
if new_direction != last_direction {
300+
direction_changes += 1;
301+
}
302+
last_direction = new_direction;
303+
}
304+
305+
assert!(
306+
direction_changes > 50,
307+
"Stream should change direction frequently, only changed {} times",
308+
direction_changes
309+
);
310+
}
311+
312+
#[test]
313+
fn test_stream_bounds() {
314+
let mut stream = Stream::new(80, 24);
315+
for _ in 0..10000 {
316+
stream.update(80, 24);
317+
assert!(
318+
stream.x >= 1 && stream.x <= 78,
319+
"X out of bounds: {}",
320+
stream.x
321+
);
322+
assert!(stream.y >= 1, "Y below minimum: {}", stream.y);
323+
}
324+
}
325+
326+
#[test]
327+
fn test_color_distribution() {
328+
let mut color_counts = std::collections::HashMap::new();
329+
for _ in 0..10000 {
330+
let color = random_color();
331+
*color_counts.entry(format!("{:?}", color)).or_insert(0) += 1;
332+
}
333+
334+
// Check that each color appeared at least once
335+
assert!(
336+
color_counts.len() >= COLORS.len(),
337+
"Not all colors appeared: {:?}",
338+
color_counts
339+
);
340+
341+
// Verify primary colors appear more often than accents
342+
for weight in COLORS {
343+
match weight {
344+
Weight::Primary(c, _) => {
345+
let count = color_counts.get(&format!("{:?}", c)).unwrap_or(&0);
346+
assert!(
347+
count > &500,
348+
"Primary color {:?} appeared only {} times",
349+
c,
350+
count
351+
);
352+
}
353+
Weight::Accent(c, _) => {
354+
let count = color_counts.get(&format!("{:?}", c)).unwrap_or(&0);
355+
assert!(
356+
count > &100,
357+
"Accent color {:?} appeared only {} times",
358+
c,
359+
count
360+
);
361+
}
362+
}
363+
}
364+
}
365+
366+
#[test]
367+
fn test_chaos_probability() {
368+
let mut new_streams = 0;
369+
let trials = 10000;
370+
371+
for _ in 0..trials {
372+
if rand::thread_rng().gen_bool(CHAOS) {
373+
new_streams += 1;
374+
}
375+
}
376+
377+
let actual_probability = new_streams as f64 / trials as f64;
378+
assert!(
379+
(actual_probability - CHAOS).abs() < 0.02,
380+
"Chaos probability {} significantly deviated from expected {}",
381+
actual_probability,
382+
CHAOS
383+
);
384+
}
385+
386+
#[test]
387+
fn test_random_string_content() {
388+
let s = random_string();
389+
assert!(
390+
s.chars()
391+
.all(|c| CHAR_SET.iter().any(|&set| set.contains(c))),
392+
"Invalid characters in string: {}",
393+
s
394+
);
395+
}
396+
397+
#[test]
398+
fn test_color_weights() {
399+
let total: u8 = COLORS
400+
.iter()
401+
.map(|c| match c {
402+
Weight::Primary(_, w) | Weight::Accent(_, w) => w,
403+
})
404+
.sum();
405+
assert!(total > 0, "Total color weights must be positive");
406+
407+
let mut counts = std::collections::HashMap::new();
408+
for _ in 0..1000 {
409+
let color = random_color();
410+
*counts.entry(color).or_insert(0) += 1;
411+
}
412+
413+
// Verify primary colors appear more frequently than accents
414+
for color_weight in COLORS {
415+
match color_weight {
416+
Weight::Primary(c, _) => {
417+
let count = counts.get(c).unwrap_or(&0);
418+
assert!(*count > 100, "Primary color {:?} appeared too rarely", c);
419+
}
420+
Weight::Accent(_, _) => {}
421+
}
422+
}
423+
}
424+
425+
#[test]
426+
fn test_stream_boundaries() {
427+
let mut stream = Stream::new(80, 24);
428+
429+
// Test multiple updates to ensure boundaries are respected
430+
for _ in 0..1000 {
431+
stream.update(80, 24);
432+
assert!(
433+
stream.x > 0 && stream.x < 79,
434+
"X position out of bounds: {}",
435+
stream.x
436+
);
437+
assert!(stream.y > 0, "Y position below zero: {}", stream.y);
438+
}
439+
}
440+
441+
#[test]
442+
fn test_direction_distribution() {
443+
let mut counts = std::collections::HashMap::new();
444+
for _ in 0..1000 {
445+
let dir = Direction::random();
446+
let offset = dir.get_offset();
447+
*counts.entry(offset).or_insert(0) += 1;
448+
}
449+
450+
// Check that all directions are used
451+
assert_eq!(counts.len(), 8, "Not all directions were generated");
452+
453+
// Check for roughly even distribution
454+
for (_offset, count) in counts {
455+
assert!(count > 50, "Direction appeared too rarely: {} times", count);
456+
}
457+
}
458+
459+
#[test]
460+
fn test_stream_movement() {
461+
let mut stream = Stream::new(80, 24);
462+
let initial_pos = (stream.x, stream.y);
463+
464+
// Store a few positions to verify movement
465+
let mut positions = vec![initial_pos];
466+
for _ in 0..10 {
467+
stream.update(80, 24);
468+
positions.push((stream.x, stream.y));
469+
}
470+
471+
// Verify that the stream actually moved
472+
assert!(
473+
positions.windows(2).any(|w| w[0] != w[1]),
474+
"Stream didn't move from initial position"
475+
);
476+
}
477+
}

0 commit comments

Comments
 (0)