pakdo 0.0.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.
pakdo-0.0.0/PKG-INFO ADDED
@@ -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
+ ```
pakdo-0.0.0/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # pakdo
2
+
3
+ Une petite bibliothèque Python, sans dépendance, qui affiche dans le terminal
4
+ l'animation d'une goutte d'eau qui tombe puis éclabousse la surface.
5
+
6
+ ```
7
+ .
8
+ / \
9
+ \_/
10
+
11
+
12
+
13
+ '
14
+ ( ' ' )
15
+ ~~~~~~~~~~(~~~~~~~~~)~~~~~~~~~~
16
+ ```
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ pip install pakdo==0.0.0
22
+ ```
23
+
24
+ ## Utilisation
25
+
26
+ En ligne de commande :
27
+
28
+ ```bash
29
+ python -m pakdo # une chute
30
+ python -m pakdo --boucles 0 # en boucle (Ctrl+C pour arrêter)
31
+ python -m pakdo --largeur 41 --hauteur 20 --fps 30 --sans-couleur
32
+ ```
33
+
34
+ Depuis Python :
35
+
36
+ ```python
37
+ from pakdo import Goutte, animer
38
+
39
+ animer() # animation par défaut
40
+ Goutte(largeur=41, hauteur=20).jouer(fps=30, boucles=3)
41
+
42
+ # Récupérer les images pour les afficher ailleurs
43
+ images = Goutte().frames()
44
+ print(images[0])
45
+ ```
46
+
47
+ ## Paramètres de `Goutte`
48
+
49
+ | Paramètre | Défaut | Description |
50
+ |-----------|--------|----------------------------------------------|
51
+ | `largeur` | 31 | nombre de colonnes (minimum 11) |
52
+ | `hauteur` | 16 | nombre de lignes, surface comprise (min. 6) |
53
+ | `gravite` | 0.35 | accélération de la chute |
54
+ | `couleur` | True | affichage en bleu (codes ANSI) |
55
+
56
+ `jouer(fps=20, boucles=1)` : `boucles=0` répète à l'infini.
57
+
58
+ ## Tests
59
+
60
+ ```bash
61
+ python -m unittest discover tests
62
+ ```
@@ -0,0 +1,6 @@
1
+ """pakdo — une petite animation de goutte d'eau qui tombe, dans le terminal."""
2
+
3
+ from .animation import Goutte, animer
4
+
5
+ __all__ = ["Goutte", "animer"]
6
+ __version__ = "0.0.0"
@@ -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()
@@ -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,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ pakdo/__init__.py
4
+ pakdo/__main__.py
5
+ pakdo/animation.py
6
+ pakdo.egg-info/PKG-INFO
7
+ pakdo.egg-info/SOURCES.txt
8
+ pakdo.egg-info/dependency_links.txt
9
+ pakdo.egg-info/entry_points.txt
10
+ pakdo.egg-info/top_level.txt
11
+ tests/test_animation.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pakdo = pakdo.__main__:main
@@ -0,0 +1 @@
1
+ pakdo
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pakdo"
7
+ version = "0.0.0"
8
+ description = "Une petite animation de goutte d'eau qui tombe, dans le terminal."
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ dependencies = []
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "Operating System :: OS Independent",
15
+ "Environment :: Console",
16
+ "Natural Language :: French",
17
+ ]
18
+
19
+ [project.scripts]
20
+ pakdo = "pakdo.__main__:main"
21
+
22
+ [tool.setuptools]
23
+ packages = ["pakdo"]
pakdo-0.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,44 @@
1
+ import io
2
+ import os
3
+ import sys
4
+ import unittest
5
+
6
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
7
+
8
+ from pakdo import Goutte
9
+
10
+
11
+ class TestGoutte(unittest.TestCase):
12
+ def test_toutes_les_images_ont_la_meme_taille(self):
13
+ g = Goutte(largeur=21, hauteur=10)
14
+ for image in g.frames():
15
+ lignes = image.split("\n")
16
+ self.assertEqual(len(lignes), 10)
17
+ self.assertTrue(all(len(l) == 21 for l in lignes))
18
+
19
+ def test_la_goutte_part_du_haut(self):
20
+ premiere = Goutte().frames()[0].split("\n")
21
+ self.assertIn("/ \\", premiere[1])
22
+
23
+ def test_la_derniere_image_est_calme(self):
24
+ g = Goutte(largeur=15, hauteur=8)
25
+ derniere = g.frames()[-1].split("\n")
26
+ self.assertTrue(all(l.strip() == "" for l in derniere[:-1]))
27
+ self.assertEqual(derniere[-1], "~" * 15)
28
+
29
+ def test_parametres_invalides(self):
30
+ with self.assertRaises(ValueError):
31
+ Goutte(largeur=5)
32
+ with self.assertRaises(ValueError):
33
+ Goutte(hauteur=2)
34
+ with self.assertRaises(ValueError):
35
+ Goutte(gravite=0)
36
+
37
+ def test_jouer_ecrit_dans_la_sortie(self):
38
+ sortie = io.StringIO()
39
+ Goutte(couleur=False).jouer(fps=1000, sortie=sortie)
40
+ self.assertIn("\\_/", sortie.getvalue())
41
+
42
+
43
+ if __name__ == "__main__":
44
+ unittest.main()