firefly-meadow 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rahul Bantupalli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: firefly-meadow
3
+ Version: 0.1.0
4
+ Summary: A terminal ASCII firefly meadow. Wave your mouse, leave a rainbow trail.
5
+ Author: Rahul Bantupalli
6
+ License-Expression: MIT
7
+ Keywords: terminal,ascii,game,fireflies,iterm,toy
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: MacOS
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Environment :: Console
12
+ Classifier: Topic :: Games/Entertainment
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Dynamic: license-file
17
+
18
+ # firefly-meadow
19
+
20
+ A tiny ASCII firefly meadow for your terminal. Move your mouse over the window:
21
+ the grass rustles and fireflies burst out, leaving a rainbow trail behind your cursor.
22
+
23
+ ## Install and play
24
+
25
+ ```
26
+ pip install firefly-meadow
27
+ fireflies
28
+ ```
29
+
30
+ Or run it without installing:
31
+
32
+ ```
33
+ pipx run firefly-meadow
34
+ # or
35
+ uvx firefly-meadow
36
+ ```
37
+
38
+ ## Controls
39
+
40
+ | Input | What it does |
41
+ |--------------|------------------------------|
42
+ | Mouse move | Disturb the grass, leave a trail of glowing fireflies |
43
+ | Left click | Big burst of fireflies |
44
+ | `c` | Clear the screen |
45
+ | `q`/`Ctrl-C` | Quit |
46
+
47
+ ## Requirements
48
+
49
+ - macOS or Linux (it uses `termios`, so no native Windows; WSL works)
50
+ - A terminal with truecolor and mouse reporting: iTerm2, Kitty, WezTerm, Alacritty,
51
+ GNOME Terminal, and most modern terminals
52
+ - Python 3.8+, no other dependencies
53
+
54
+ Tips: make the window fullscreen for the best effect. In iTerm2, enable
55
+ Preferences > Profiles > Terminal > "Report mouse clicks & drags" if the mouse
56
+ does nothing. Inside tmux, turn on `set -g mouse on`.
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,43 @@
1
+ # firefly-meadow
2
+
3
+ A tiny ASCII firefly meadow for your terminal. Move your mouse over the window:
4
+ the grass rustles and fireflies burst out, leaving a rainbow trail behind your cursor.
5
+
6
+ ## Install and play
7
+
8
+ ```
9
+ pip install firefly-meadow
10
+ fireflies
11
+ ```
12
+
13
+ Or run it without installing:
14
+
15
+ ```
16
+ pipx run firefly-meadow
17
+ # or
18
+ uvx firefly-meadow
19
+ ```
20
+
21
+ ## Controls
22
+
23
+ | Input | What it does |
24
+ |--------------|------------------------------|
25
+ | Mouse move | Disturb the grass, leave a trail of glowing fireflies |
26
+ | Left click | Big burst of fireflies |
27
+ | `c` | Clear the screen |
28
+ | `q`/`Ctrl-C` | Quit |
29
+
30
+ ## Requirements
31
+
32
+ - macOS or Linux (it uses `termios`, so no native Windows; WSL works)
33
+ - A terminal with truecolor and mouse reporting: iTerm2, Kitty, WezTerm, Alacritty,
34
+ GNOME Terminal, and most modern terminals
35
+ - Python 3.8+, no other dependencies
36
+
37
+ Tips: make the window fullscreen for the best effect. In iTerm2, enable
38
+ Preferences > Profiles > Terminal > "Report mouse clicks & drags" if the mouse
39
+ does nothing. Inside tmux, turn on `set -g mouse on`.
40
+
41
+ ## License
42
+
43
+ MIT
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "firefly-meadow"
7
+ version = "0.1.0"
8
+ description = "A terminal ASCII firefly meadow. Wave your mouse, leave a rainbow trail."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Rahul Bantupalli" }]
14
+ keywords = ["terminal", "ascii", "game", "fireflies", "iterm", "toy"]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Operating System :: MacOS",
18
+ "Operating System :: POSIX :: Linux",
19
+ "Environment :: Console",
20
+ "Topic :: Games/Entertainment",
21
+ ]
22
+
23
+ [project.scripts]
24
+ fireflies = "firefly_meadow.game:main"
25
+
26
+ [tool.setuptools.packages.find]
27
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """firefly-meadow: a terminal ASCII firefly game."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ firefly_meadow - a tiny ASCII firefly meadow for iTerm2 / any truecolor terminal.
4
+
5
+ Move your mouse over the terminal: the grass rustles and fireflies burst out,
6
+ leaving a rainbow trail. Click for a big burst of fireflies.
7
+
8
+ Keys: q = quit c = clear Ctrl-C = quit
9
+ Run: fireflies
10
+ """
11
+ import colorsys
12
+ import math
13
+ import os
14
+ import random
15
+ import re
16
+ import select
17
+ import shutil
18
+ import sys
19
+ import time
20
+
21
+ try:
22
+ import termios
23
+ import tty
24
+ except ImportError: # Windows
25
+ termios = None
26
+ tty = None
27
+
28
+ FPS = 30
29
+ MAX_PARTICLES = 1800
30
+ GRASS_CHARS = "|'\",;!"
31
+ MOUSE_RE = re.compile(r"\x1b\[<(\d+);(\d+);(\d+)([Mm])")
32
+
33
+ ESC = "\x1b"
34
+
35
+
36
+ def hsv(h, s, v):
37
+ r, g, b = colorsys.hsv_to_rgb(h % 1.0, s, v)
38
+ return r, g, b
39
+
40
+
41
+ class Particle:
42
+ __slots__ = ("x", "y", "vx", "vy", "life", "maxlife", "hue", "phase")
43
+
44
+ def __init__(self, x, y, hue, burst=False):
45
+ self.x = x + random.uniform(-0.5, 0.5)
46
+ self.y = y + random.uniform(-0.3, 0.3)
47
+ spread = 1.4 if burst else 0.35
48
+ self.vx = random.uniform(-spread, spread)
49
+ self.vy = random.uniform(-spread * 0.5, spread * 0.2)
50
+ self.maxlife = random.uniform(30, 90) if not burst else random.uniform(40, 110)
51
+ self.life = self.maxlife
52
+ self.hue = hue + random.uniform(-0.04, 0.04)
53
+ self.phase = random.uniform(0, 6.28)
54
+
55
+
56
+ class Ambient:
57
+ """Faint yellow-green fireflies that blink on their own."""
58
+
59
+ def __init__(self, w, h):
60
+ self.x = random.uniform(0, w)
61
+ self.y = random.uniform(0, h)
62
+ self.phase = random.uniform(0, 6.28)
63
+ self.speed = random.uniform(0.5, 1.2)
64
+
65
+
66
+ def make_grass(w, h):
67
+ rng = random.Random(7)
68
+ return [
69
+ [rng.choice(GRASS_CHARS) if rng.random() < 0.28 else " " for _ in range(w)]
70
+ for _ in range(h)
71
+ ]
72
+
73
+
74
+ def main():
75
+ if termios is None:
76
+ sys.exit("firefly-meadow needs macOS or Linux (or WSL on Windows).")
77
+ if not (sys.stdin.isatty() and sys.stdout.isatty()):
78
+ sys.exit("firefly-meadow must be run in an interactive terminal.")
79
+ fd = sys.stdin.fileno()
80
+ old = termios.tcgetattr(fd)
81
+ out = sys.stdout
82
+
83
+ w, h = shutil.get_terminal_size()
84
+ grass = make_grass(w, h)
85
+ particles = []
86
+ ambient = [Ambient(w, h) for _ in range(16)]
87
+ disturbed = {} # (x, y) -> frames left
88
+ last_mouse = None
89
+ hue_base = random.random()
90
+ frame = 0
91
+ running = True
92
+
93
+ def spawn_trail(x0, y0, x1, y1):
94
+ nonlocal hue_base
95
+ steps = max(abs(x1 - x0), abs(y1 - y0), 1)
96
+ for i in range(steps):
97
+ t = (i + 1) / steps
98
+ cx = x0 + (x1 - x0) * t
99
+ cy = y0 + (y1 - y0) * t
100
+ hue_base += 0.006
101
+ for _ in range(random.choice((1, 2, 2, 3))):
102
+ if len(particles) < MAX_PARTICLES:
103
+ particles.append(Particle(cx, cy, hue_base))
104
+ # rustle the grass around the cursor
105
+ for dy in range(-2, 3):
106
+ for dx in range(-3, 4):
107
+ if dx * dx / 2.2 + dy * dy <= 4:
108
+ disturbed[(int(cx) + dx, int(cy) + dy)] = 14
109
+
110
+ def burst(x, y):
111
+ nonlocal hue_base
112
+ for _ in range(70):
113
+ if len(particles) < MAX_PARTICLES:
114
+ particles.append(Particle(x, y, hue_base + random.random() * 0.5, burst=True))
115
+
116
+ try:
117
+ tty.setcbreak(fd)
118
+ # alt screen, hide cursor, no autowrap, mouse: any-motion + SGR coords
119
+ out.write(f"{ESC}[?1049h{ESC}[?25l{ESC}[?7l{ESC}[?1003h{ESC}[?1006h{ESC}[2J")
120
+ out.flush()
121
+
122
+ while running:
123
+ t0 = time.time()
124
+ frame += 1
125
+
126
+ # ---- resize ----
127
+ nw, nh = shutil.get_terminal_size()
128
+ if (nw, nh) != (w, h):
129
+ w, h = nw, nh
130
+ grass = make_grass(w, h)
131
+ out.write(f"{ESC}[2J")
132
+
133
+ # ---- input ----
134
+ while select.select([fd], [], [], 0)[0]:
135
+ data = os.read(fd, 4096).decode("utf-8", "ignore")
136
+ for m in MOUSE_RE.finditer(data):
137
+ b, mx, my, kind = int(m.group(1)), int(m.group(2)) - 1, int(m.group(3)) - 1, m.group(4)
138
+ if last_mouse is None:
139
+ last_mouse = (mx, my)
140
+ spawn_trail(last_mouse[0], last_mouse[1], mx, my)
141
+ last_mouse = (mx, my)
142
+ if kind == "M" and (b & 3) == 0 and not (b & 32):
143
+ burst(mx, my)
144
+ rest = MOUSE_RE.sub("", data)
145
+ if "q" in rest or "\x03" in rest:
146
+ running = False
147
+ if "c" in rest:
148
+ particles.clear()
149
+ disturbed.clear()
150
+
151
+ # ---- update ----
152
+ now = time.time()
153
+ for p in particles:
154
+ p.x += p.vx
155
+ p.y += p.vy * 0.6
156
+ p.vx += random.uniform(-0.06, 0.06)
157
+ p.vy += random.uniform(-0.05, 0.03) - 0.004
158
+ p.vx *= 0.95
159
+ p.vy *= 0.95
160
+ p.life -= 1
161
+ particles[:] = [p for p in particles if p.life > 0 and -2 < p.x < w + 2 and -2 < p.y < h + 2]
162
+
163
+ for a in ambient:
164
+ a.x += random.uniform(-0.15, 0.15) * a.speed
165
+ a.y += random.uniform(-0.08, 0.08) * a.speed
166
+ a.x %= w
167
+ a.y %= h
168
+
169
+ for k in list(disturbed):
170
+ disturbed[k] -= 1
171
+ if disturbed[k] <= 0:
172
+ del disturbed[k]
173
+
174
+ # ---- glow buffer: cell -> [r, g, b, char] ----
175
+ glow = {}
176
+
177
+ def add(x, y, rgb, strength, char=None):
178
+ if 0 <= x < w and 0 <= y < h:
179
+ c = glow.setdefault((x, y), [0.0, 0.0, 0.0, None])
180
+ c[0] = min(1.0, c[0] + rgb[0] * strength)
181
+ c[1] = min(1.0, c[1] + rgb[1] * strength)
182
+ c[2] = min(1.0, c[2] + rgb[2] * strength)
183
+ if char and (c[3] is None or char in "*O@"):
184
+ c[3] = char
185
+
186
+ for p in particles:
187
+ b = p.life / p.maxlife
188
+ b *= 0.65 + 0.35 * math.sin(now * 9 + p.phase)
189
+ b = max(0.0, b)
190
+ rgb = hsv(p.hue, 0.65, 1.0)
191
+ ch = "O" if b > 0.8 else "*" if b > 0.5 else "+" if b > 0.28 else "."
192
+ cx, cy = int(p.x), int(p.y)
193
+ add(cx, cy, rgb, min(1.0, b * 1.2), ch)
194
+ for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
195
+ add(cx + dx, cy + dy, rgb, b * 0.35)
196
+
197
+ for a in ambient:
198
+ b = max(0.0, math.sin(now * 0.9 * a.speed + a.phase)) ** 3
199
+ if b > 0.05:
200
+ rgb = hsv(0.17, 0.75, 1.0)
201
+ add(int(a.x), int(a.y), rgb, b * 0.7, "*" if b > 0.5 else ".")
202
+ add(int(a.x) + 1, int(a.y), rgb, b * 0.2)
203
+ add(int(a.x) - 1, int(a.y), rgb, b * 0.2)
204
+
205
+ # ---- render ----
206
+ buf = []
207
+ for y in range(h):
208
+ buf.append(f"{ESC}[{y + 1};1H")
209
+ last = None
210
+ for x in range(w):
211
+ g = grass[y][x]
212
+ cell = glow.get((x, y))
213
+ if (x, y) in disturbed and g != " ":
214
+ g = "/" if (x + frame // 3) % 2 == 0 else "\\"
215
+ if cell:
216
+ r, gg, bb, ch = cell
217
+ base = (12, 45, 18) if g != " " else (0, 0, 0)
218
+ R = min(255, int(base[0] + r * 255))
219
+ G = min(255, int(base[1] + gg * 255))
220
+ B = min(255, int(base[2] + bb * 255))
221
+ char = ch if ch else (g if g != " " else ("." if (r + gg + bb) > 0.5 else " "))
222
+ col = (R, G, B)
223
+ elif g != " ":
224
+ char = g
225
+ col = (30, 95, 40) if (x, y) in disturbed else (12, 45, 18)
226
+ else:
227
+ char = " "
228
+ col = None
229
+ if col != last:
230
+ buf.append(f"{ESC}[38;2;{col[0]};{col[1]};{col[2]}m" if col else f"{ESC}[0m")
231
+ last = col
232
+ buf.append(char)
233
+ buf.append(f"{ESC}[0m")
234
+ out.write("".join(buf))
235
+ out.write(f"{ESC}[1;2H{ESC}[38;2;80;80;80mmove mouse | click = burst | c = clear | q = quit{ESC}[0m")
236
+ out.flush()
237
+
238
+ dt = time.time() - t0
239
+ if dt < 1 / FPS:
240
+ time.sleep(1 / FPS - dt)
241
+
242
+ except KeyboardInterrupt:
243
+ pass
244
+ finally:
245
+ out.write(f"{ESC}[?1003l{ESC}[?1006l{ESC}[?7h{ESC}[?25h{ESC}[0m{ESC}[?1049l")
246
+ out.flush()
247
+ termios.tcsetattr(fd, termios.TCSADRAIN, old)
248
+
249
+
250
+ if __name__ == "__main__":
251
+ main()
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: firefly-meadow
3
+ Version: 0.1.0
4
+ Summary: A terminal ASCII firefly meadow. Wave your mouse, leave a rainbow trail.
5
+ Author: Rahul Bantupalli
6
+ License-Expression: MIT
7
+ Keywords: terminal,ascii,game,fireflies,iterm,toy
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: MacOS
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Environment :: Console
12
+ Classifier: Topic :: Games/Entertainment
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Dynamic: license-file
17
+
18
+ # firefly-meadow
19
+
20
+ A tiny ASCII firefly meadow for your terminal. Move your mouse over the window:
21
+ the grass rustles and fireflies burst out, leaving a rainbow trail behind your cursor.
22
+
23
+ ## Install and play
24
+
25
+ ```
26
+ pip install firefly-meadow
27
+ fireflies
28
+ ```
29
+
30
+ Or run it without installing:
31
+
32
+ ```
33
+ pipx run firefly-meadow
34
+ # or
35
+ uvx firefly-meadow
36
+ ```
37
+
38
+ ## Controls
39
+
40
+ | Input | What it does |
41
+ |--------------|------------------------------|
42
+ | Mouse move | Disturb the grass, leave a trail of glowing fireflies |
43
+ | Left click | Big burst of fireflies |
44
+ | `c` | Clear the screen |
45
+ | `q`/`Ctrl-C` | Quit |
46
+
47
+ ## Requirements
48
+
49
+ - macOS or Linux (it uses `termios`, so no native Windows; WSL works)
50
+ - A terminal with truecolor and mouse reporting: iTerm2, Kitty, WezTerm, Alacritty,
51
+ GNOME Terminal, and most modern terminals
52
+ - Python 3.8+, no other dependencies
53
+
54
+ Tips: make the window fullscreen for the best effect. In iTerm2, enable
55
+ Preferences > Profiles > Terminal > "Report mouse clicks & drags" if the mouse
56
+ does nothing. Inside tmux, turn on `set -g mouse on`.
57
+
58
+ ## License
59
+
60
+ MIT
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/firefly_meadow/__init__.py
5
+ src/firefly_meadow/game.py
6
+ src/firefly_meadow.egg-info/PKG-INFO
7
+ src/firefly_meadow.egg-info/SOURCES.txt
8
+ src/firefly_meadow.egg-info/dependency_links.txt
9
+ src/firefly_meadow.egg-info/entry_points.txt
10
+ src/firefly_meadow.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ fireflies = firefly_meadow.game:main
@@ -0,0 +1 @@
1
+ firefly_meadow