pakdo 0.0.0__py3-none-any.whl
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.
- pakdo/__init__.py +6 -0
- pakdo/__main__.py +26 -0
- pakdo/animation.py +162 -0
- pakdo-0.0.0.dist-info/METADATA +73 -0
- pakdo-0.0.0.dist-info/RECORD +8 -0
- pakdo-0.0.0.dist-info/WHEEL +5 -0
- pakdo-0.0.0.dist-info/entry_points.txt +2 -0
- pakdo-0.0.0.dist-info/top_level.txt +1 -0
pakdo/__init__.py
ADDED
pakdo/__main__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Point d'entrée en ligne de commande : ``python -m pakdo``."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from .animation import Goutte
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main() -> None:
|
|
9
|
+
parser = argparse.ArgumentParser(prog="pakdo", description="Animation d'une goutte d'eau qui tombe.")
|
|
10
|
+
parser.add_argument("--largeur", type=int, default=31, help="largeur de la scène (défaut : 31)")
|
|
11
|
+
parser.add_argument("--hauteur", type=int, default=16, help="hauteur de la scène (défaut : 16)")
|
|
12
|
+
parser.add_argument("--gravite", type=float, default=0.35, help="accélération de la chute (défaut : 0.35)")
|
|
13
|
+
parser.add_argument("--fps", type=float, default=20, help="images par seconde (défaut : 20)")
|
|
14
|
+
parser.add_argument("--boucles", type=int, default=1, help="nombre de répétitions, 0 = infini (défaut : 1)")
|
|
15
|
+
parser.add_argument("--sans-couleur", action="store_true", help="désactive la couleur")
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
goutte = Goutte(args.largeur, args.hauteur, args.gravite, couleur=not args.sans_couleur)
|
|
20
|
+
goutte.jouer(fps=args.fps, boucles=args.boucles)
|
|
21
|
+
except ValueError as e:
|
|
22
|
+
parser.error(str(e))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if __name__ == "__main__":
|
|
26
|
+
main()
|
pakdo/animation.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Génération et lecture de l'animation d'une goutte d'eau qui tombe."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import time
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import Iterator, List, Optional, TextIO
|
|
10
|
+
|
|
11
|
+
# Dessin de la goutte (3 lignes x 3 colonnes).
|
|
12
|
+
SPRITE_GOUTTE = [
|
|
13
|
+
" . ",
|
|
14
|
+
"/ \\",
|
|
15
|
+
"\\_/",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
EAU = "~"
|
|
19
|
+
VIDE = " "
|
|
20
|
+
|
|
21
|
+
_BLEU = "\033[36m"
|
|
22
|
+
_RESET = "\033[0m"
|
|
23
|
+
_CURSEUR_ACCUEIL = "\033[H"
|
|
24
|
+
_EFFACER_ECRAN = "\033[2J"
|
|
25
|
+
_CACHER_CURSEUR = "\033[?25l"
|
|
26
|
+
_MONTRER_CURSEUR = "\033[?25h"
|
|
27
|
+
|
|
28
|
+
Grille = List[List[str]]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class Goutte:
|
|
33
|
+
"""Animation d'une goutte qui tombe puis éclabousse la surface de l'eau.
|
|
34
|
+
|
|
35
|
+
:param largeur: nombre de colonnes de la scène (minimum 11).
|
|
36
|
+
:param hauteur: nombre de lignes de la scène, surface de l'eau comprise (minimum 6).
|
|
37
|
+
:param gravite: accélération de la chute, en lignes par image².
|
|
38
|
+
:param couleur: affiche l'animation en bleu (codes ANSI).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
largeur: int = 31
|
|
42
|
+
hauteur: int = 16
|
|
43
|
+
gravite: float = 0.35
|
|
44
|
+
couleur: bool = True
|
|
45
|
+
|
|
46
|
+
def __post_init__(self) -> None:
|
|
47
|
+
if self.largeur < 11:
|
|
48
|
+
raise ValueError("la largeur doit être au moins 11")
|
|
49
|
+
if self.hauteur < 6:
|
|
50
|
+
raise ValueError("la hauteur doit être au moins 6")
|
|
51
|
+
if self.gravite <= 0:
|
|
52
|
+
raise ValueError("la gravité doit être strictement positive")
|
|
53
|
+
|
|
54
|
+
# ------------------------------------------------------------------ images
|
|
55
|
+
|
|
56
|
+
def frames(self) -> List[str]:
|
|
57
|
+
"""Retourne toutes les images de l'animation (chaînes multi-lignes)."""
|
|
58
|
+
return list(self._chute()) + list(self._eclaboussure())
|
|
59
|
+
|
|
60
|
+
def _chute(self) -> Iterator[str]:
|
|
61
|
+
# La goutte s'arrête juste au-dessus de la surface.
|
|
62
|
+
fin = self.hauteur - 1 - len(SPRITE_GOUTTE)
|
|
63
|
+
t = 0
|
|
64
|
+
while True:
|
|
65
|
+
y = min(int(0.5 * self.gravite * t * t), fin)
|
|
66
|
+
grille = self._grille_vide()
|
|
67
|
+
self._dessiner_sprite(grille, SPRITE_GOUTTE, y, self.largeur // 2 - 1)
|
|
68
|
+
yield self._rendu(grille)
|
|
69
|
+
if y >= fin:
|
|
70
|
+
return
|
|
71
|
+
t += 1
|
|
72
|
+
|
|
73
|
+
def _eclaboussure(self) -> Iterator[str]:
|
|
74
|
+
cx = self.largeur // 2
|
|
75
|
+
surface = self.hauteur - 1
|
|
76
|
+
# (hauteur au-dessus de la surface, caractère) des gouttelettes projetées
|
|
77
|
+
gouttelettes = {1: (1, "'"), 2: (2, "'"), 3: (2, "."), 4: (1, ".")}
|
|
78
|
+
|
|
79
|
+
k = 1
|
|
80
|
+
while 2 * k - 4 <= cx:
|
|
81
|
+
grille = self._grille_vide()
|
|
82
|
+
if k == 1:
|
|
83
|
+
self._poser(grille, surface - 1, cx - 1, "\\")
|
|
84
|
+
self._poser(grille, surface - 1, cx + 1, "/")
|
|
85
|
+
if k in gouttelettes:
|
|
86
|
+
h, c = gouttelettes[k]
|
|
87
|
+
self._poser(grille, surface - h, cx - (k + 1), c)
|
|
88
|
+
self._poser(grille, surface - h, cx + (k + 1), c)
|
|
89
|
+
for r in (2 * k, 2 * k - 4):
|
|
90
|
+
if r > 0:
|
|
91
|
+
self._poser(grille, surface, cx - r, "(")
|
|
92
|
+
self._poser(grille, surface, cx + r, ")")
|
|
93
|
+
yield self._rendu(grille)
|
|
94
|
+
k += 1
|
|
95
|
+
|
|
96
|
+
# Surface redevenue calme.
|
|
97
|
+
yield self._rendu(self._grille_vide())
|
|
98
|
+
|
|
99
|
+
# ------------------------------------------------------------ utilitaires
|
|
100
|
+
|
|
101
|
+
def _grille_vide(self) -> Grille:
|
|
102
|
+
grille = [[VIDE] * self.largeur for _ in range(self.hauteur - 1)]
|
|
103
|
+
grille.append([EAU] * self.largeur)
|
|
104
|
+
return grille
|
|
105
|
+
|
|
106
|
+
def _poser(self, grille: Grille, y: int, x: int, c: str) -> None:
|
|
107
|
+
if 0 <= y < self.hauteur and 0 <= x < self.largeur:
|
|
108
|
+
grille[y][x] = c
|
|
109
|
+
|
|
110
|
+
def _dessiner_sprite(self, grille: Grille, sprite: List[str], haut: int, gauche: int) -> None:
|
|
111
|
+
for dy, ligne in enumerate(sprite):
|
|
112
|
+
for dx, c in enumerate(ligne):
|
|
113
|
+
if c != VIDE:
|
|
114
|
+
self._poser(grille, haut + dy, gauche + dx, c)
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def _rendu(grille: Grille) -> str:
|
|
118
|
+
return "\n".join("".join(ligne) for ligne in grille)
|
|
119
|
+
|
|
120
|
+
# ---------------------------------------------------------------- lecture
|
|
121
|
+
|
|
122
|
+
def jouer(self, fps: float = 20, boucles: int = 1, sortie: Optional[TextIO] = None) -> None:
|
|
123
|
+
"""Joue l'animation dans le terminal.
|
|
124
|
+
|
|
125
|
+
:param fps: images par seconde.
|
|
126
|
+
:param boucles: nombre de répétitions (0 = infini, arrêt avec Ctrl+C).
|
|
127
|
+
:param sortie: flux de sortie (``sys.stdout`` par défaut).
|
|
128
|
+
"""
|
|
129
|
+
if fps <= 0:
|
|
130
|
+
raise ValueError("fps doit être strictement positif")
|
|
131
|
+
sortie = sortie or sys.stdout
|
|
132
|
+
_activer_ansi()
|
|
133
|
+
images = self.frames()
|
|
134
|
+
delai = 1 / fps
|
|
135
|
+
|
|
136
|
+
sortie.write(_CACHER_CURSEUR + _EFFACER_ECRAN)
|
|
137
|
+
try:
|
|
138
|
+
n = 0
|
|
139
|
+
while boucles <= 0 or n < boucles:
|
|
140
|
+
for image in images:
|
|
141
|
+
if self.couleur:
|
|
142
|
+
image = _BLEU + image + _RESET
|
|
143
|
+
sortie.write(_CURSEUR_ACCUEIL + image)
|
|
144
|
+
sortie.flush()
|
|
145
|
+
time.sleep(delai)
|
|
146
|
+
n += 1
|
|
147
|
+
except KeyboardInterrupt:
|
|
148
|
+
pass
|
|
149
|
+
finally:
|
|
150
|
+
sortie.write(_RESET + _MONTRER_CURSEUR + "\n")
|
|
151
|
+
sortie.flush()
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _activer_ansi() -> None:
|
|
155
|
+
"""Active l'interprétation des codes ANSI dans la console Windows."""
|
|
156
|
+
if os.name == "nt":
|
|
157
|
+
os.system("")
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def animer(fps: float = 20, boucles: int = 1, **options) -> None:
|
|
161
|
+
"""Raccourci : crée une :class:`Goutte` avec ``options`` et la joue."""
|
|
162
|
+
Goutte(**options).jouer(fps=fps, boucles=boucles)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pakdo
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Une petite animation de goutte d'eau qui tombe, dans le terminal.
|
|
5
|
+
Classifier: Programming Language :: Python :: 3
|
|
6
|
+
Classifier: Operating System :: OS Independent
|
|
7
|
+
Classifier: Environment :: Console
|
|
8
|
+
Classifier: Natural Language :: French
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# pakdo
|
|
13
|
+
|
|
14
|
+
Une petite bibliothèque Python, sans dépendance, qui affiche dans le terminal
|
|
15
|
+
l'animation d'une goutte d'eau qui tombe puis éclabousse la surface.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
.
|
|
19
|
+
/ \
|
|
20
|
+
\_/
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
'
|
|
25
|
+
( ' ' )
|
|
26
|
+
~~~~~~~~~~(~~~~~~~~~)~~~~~~~~~~
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install pakdo==0.0.0
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Utilisation
|
|
36
|
+
|
|
37
|
+
En ligne de commande :
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python -m pakdo # une chute
|
|
41
|
+
python -m pakdo --boucles 0 # en boucle (Ctrl+C pour arrêter)
|
|
42
|
+
python -m pakdo --largeur 41 --hauteur 20 --fps 30 --sans-couleur
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Depuis Python :
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from pakdo import Goutte, animer
|
|
49
|
+
|
|
50
|
+
animer() # animation par défaut
|
|
51
|
+
Goutte(largeur=41, hauteur=20).jouer(fps=30, boucles=3)
|
|
52
|
+
|
|
53
|
+
# Récupérer les images pour les afficher ailleurs
|
|
54
|
+
images = Goutte().frames()
|
|
55
|
+
print(images[0])
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Paramètres de `Goutte`
|
|
59
|
+
|
|
60
|
+
| Paramètre | Défaut | Description |
|
|
61
|
+
|-----------|--------|----------------------------------------------|
|
|
62
|
+
| `largeur` | 31 | nombre de colonnes (minimum 11) |
|
|
63
|
+
| `hauteur` | 16 | nombre de lignes, surface comprise (min. 6) |
|
|
64
|
+
| `gravite` | 0.35 | accélération de la chute |
|
|
65
|
+
| `couleur` | True | affichage en bleu (codes ANSI) |
|
|
66
|
+
|
|
67
|
+
`jouer(fps=20, boucles=1)` : `boucles=0` répète à l'infini.
|
|
68
|
+
|
|
69
|
+
## Tests
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python -m unittest discover tests
|
|
73
|
+
```
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pakdo/__init__.py,sha256=INw78S9N9DbcuIduJ35MkOxna_M3c9FfzU5uhNaIWDQ,175
|
|
2
|
+
pakdo/__main__.py,sha256=eHBWrf-Wrjo_F_HrbcneZ-zE35Kd53oDVjtBFxixMlw,1163
|
|
3
|
+
pakdo/animation.py,sha256=RmYJI79cE6UneB7vQ-FOMSVGokuFuUXEl3he5dSJ6hw,5527
|
|
4
|
+
pakdo-0.0.0.dist-info/METADATA,sha256=YFSF_DoyWSpjtO7e3VyjSfC6te3nODSDfMeZuN_RUbg,1870
|
|
5
|
+
pakdo-0.0.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
6
|
+
pakdo-0.0.0.dist-info/entry_points.txt,sha256=o9UjMGVuctJ5QhNZYL08p__iohe3ojsTe7EChMEc5t8,46
|
|
7
|
+
pakdo-0.0.0.dist-info/top_level.txt,sha256=bVIizew-hagdI_1nn3J7AUHAUXdaJPUtQ1vau_R5eog,6
|
|
8
|
+
pakdo-0.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pakdo
|