libascii 0.1.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.
- libascii/__init__.py +104 -0
- libascii/_utils/__init__.py +0 -0
- libascii/_utils/keymapping.py +60 -0
- libascii/chars.py +134 -0
- libascii/color.py +32 -0
- libascii/game.py +204 -0
- libascii/grid.py +45 -0
- libascii/py.typed +0 -0
- libascii/shapes/__init__.py +5 -0
- libascii/shapes/ellipse.py +70 -0
- libascii/shapes/line.py +42 -0
- libascii/shapes/rectangle.py +54 -0
- libascii/shapes/shape.py +8 -0
- libascii/shapes/triangle.py +84 -0
- libascii/sound.py +24 -0
- libascii-0.1.0.dist-info/METADATA +27 -0
- libascii-0.1.0.dist-info/RECORD +19 -0
- libascii-0.1.0.dist-info/WHEEL +4 -0
- libascii-0.1.0.dist-info/licenses/LICENSE +21 -0
libascii/__init__.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Literal, overload
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from .chars import CharLib
|
|
7
|
+
from .color import Color
|
|
8
|
+
from .game import Game
|
|
9
|
+
from .grid import Grid
|
|
10
|
+
from .shapes.ellipse import Ellipse
|
|
11
|
+
from .shapes.line import Line
|
|
12
|
+
from .shapes.rectangle import Rectangle
|
|
13
|
+
from .shapes.shape import Shape
|
|
14
|
+
from .shapes.triangle import Triangle
|
|
15
|
+
from .sound import Sound
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@overload
|
|
19
|
+
def __getattr__(name: Literal["Color"]) -> type[Color]: ...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@overload
|
|
23
|
+
def __getattr__(name: Literal["Grid"]) -> type[Grid]: ...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@overload
|
|
27
|
+
def __getattr__(name: Literal["CharLib"]) -> CharLib: ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@overload
|
|
31
|
+
def __getattr__(name: Literal["Game"]) -> type[Game]: ...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@overload
|
|
35
|
+
def __getattr__(name: Literal["Shape"]) -> type[Shape]: ...
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@overload
|
|
39
|
+
def __getattr__(name: Literal["Rectangle"]) -> type[Rectangle]: ...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@overload
|
|
43
|
+
def __getattr__(name: Literal["Ellipse"]) -> type[Ellipse]: ...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@overload
|
|
47
|
+
def __getattr__(name: Literal["Triangle"]) -> type[Triangle]: ...
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@overload
|
|
51
|
+
def __getattr__(name: Literal["Line"]) -> type[Line]: ...
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@overload
|
|
55
|
+
def __getattr__(name: Literal["Sound"]) -> type[Sound]: ...
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@overload
|
|
59
|
+
def __getattr__(name: str) -> object: ...
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def __getattr__(name: str) -> object:
|
|
63
|
+
match name:
|
|
64
|
+
case "Color":
|
|
65
|
+
from .color import Color
|
|
66
|
+
|
|
67
|
+
return Color
|
|
68
|
+
case "Grid":
|
|
69
|
+
from .grid import Grid
|
|
70
|
+
|
|
71
|
+
return Grid
|
|
72
|
+
case "CharLib":
|
|
73
|
+
from .chars import CharLib
|
|
74
|
+
|
|
75
|
+
return CharLib
|
|
76
|
+
case "Game":
|
|
77
|
+
from .game import Game
|
|
78
|
+
|
|
79
|
+
return Game
|
|
80
|
+
case "Shape":
|
|
81
|
+
from .shapes.shape import Shape
|
|
82
|
+
|
|
83
|
+
return Shape
|
|
84
|
+
case "Rectangle":
|
|
85
|
+
from .shapes.rectangle import Rectangle
|
|
86
|
+
|
|
87
|
+
return Rectangle
|
|
88
|
+
case "Ellipse":
|
|
89
|
+
from .shapes.ellipse import Ellipse
|
|
90
|
+
|
|
91
|
+
return Ellipse
|
|
92
|
+
case "Triangle":
|
|
93
|
+
from .shapes.triangle import Triangle
|
|
94
|
+
|
|
95
|
+
return Triangle
|
|
96
|
+
case "Line":
|
|
97
|
+
from .shapes.line import Line
|
|
98
|
+
|
|
99
|
+
return Line
|
|
100
|
+
case "Sound":
|
|
101
|
+
from .sound import Sound
|
|
102
|
+
|
|
103
|
+
return Sound
|
|
104
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
MODIFIER_MAP = {
|
|
2
|
+
"ctrl": "Control",
|
|
3
|
+
"shift": "Shift",
|
|
4
|
+
"alt": "Alt",
|
|
5
|
+
"meta": "Meta",
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
MODIFIER_ORDER = ("ctrl", "shift", "alt", "meta")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def expand_keybinding(keybinding: str) -> list[tuple[frozenset[str], str]]:
|
|
12
|
+
combinations: list[tuple[frozenset[str], str]] = []
|
|
13
|
+
for pattern in keybinding.split(" or "):
|
|
14
|
+
pattern_combos: list[tuple[frozenset[str], str | None]] = [
|
|
15
|
+
(frozenset(), None)
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
for token in [token.strip() for token in pattern.split("+")]:
|
|
19
|
+
if token.startswith("[") and token.endswith("]"):
|
|
20
|
+
modifier = token[1:-1]
|
|
21
|
+
expanded: list[tuple[frozenset[str], str | None]] = []
|
|
22
|
+
|
|
23
|
+
for modifiers, key in pattern_combos:
|
|
24
|
+
expanded.append((modifiers, key))
|
|
25
|
+
expanded.append((modifiers | {modifier}, key))
|
|
26
|
+
pattern_combos = expanded
|
|
27
|
+
elif token.startswith("{") and token.endswith("}"):
|
|
28
|
+
choices = [choice.strip() for choice in token[1:-1].split(",")]
|
|
29
|
+
expanded = []
|
|
30
|
+
for modifiers, key in pattern_combos:
|
|
31
|
+
for choice in choices:
|
|
32
|
+
expanded.append((modifiers | {choice}, key))
|
|
33
|
+
pattern_combos = expanded
|
|
34
|
+
elif token in MODIFIER_MAP:
|
|
35
|
+
pattern_combos = [
|
|
36
|
+
(modifiers | {token}, key)
|
|
37
|
+
for modifiers, key in pattern_combos
|
|
38
|
+
]
|
|
39
|
+
else:
|
|
40
|
+
pattern_combos = [
|
|
41
|
+
(modifiers, token) for modifiers, _ in pattern_combos
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
for modifiers, key in pattern_combos:
|
|
45
|
+
if key is not None:
|
|
46
|
+
combinations.append((modifiers, key))
|
|
47
|
+
return combinations
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def keybinding_to_events(keybinding: str, event_type: str) -> list[str]:
|
|
51
|
+
events: list[str] = []
|
|
52
|
+
for modifiers, key in expand_keybinding(keybinding):
|
|
53
|
+
ordered = [
|
|
54
|
+
MODIFIER_MAP[modifier]
|
|
55
|
+
for modifier in MODIFIER_ORDER
|
|
56
|
+
if modifier in modifiers
|
|
57
|
+
]
|
|
58
|
+
parts = [*ordered, event_type, key]
|
|
59
|
+
events.append(f"<{'-'.join(parts)}>")
|
|
60
|
+
return events
|
libascii/chars.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
CharLib: dict[str, dict[str | int, str]] = {
|
|
2
|
+
"MAIN": {
|
|
3
|
+
"BLANK": " ",
|
|
4
|
+
"LIGHT_SHADE": "░",
|
|
5
|
+
"MEDIUM_SHADE": "▒",
|
|
6
|
+
"DARK_SHADE": "▓",
|
|
7
|
+
"BLOCK": "█",
|
|
8
|
+
"SQUARE_BLACK": "■",
|
|
9
|
+
"SQUARE_WHITE": "□",
|
|
10
|
+
"CIRCLE_BLACK": "●",
|
|
11
|
+
"CIRCLE_WHITE": "○",
|
|
12
|
+
"DIAMOND_BLACK": "◆",
|
|
13
|
+
"DIAMOND_WHITE": "◇",
|
|
14
|
+
"TRIANGLE_UP_BLACK": "▲",
|
|
15
|
+
"TRIANGLE_UP_WHITE": "△",
|
|
16
|
+
"TRIANGLE_DOWN_BLACK": "▼",
|
|
17
|
+
"TRIANGLE_DOWN_WHITE": "▽",
|
|
18
|
+
"TRIANGLE_LEFT_BLACK": "◀",
|
|
19
|
+
"TRIANGLE_LEFT_WHITE": "◁",
|
|
20
|
+
"TRIANGLE_RIGHT_BLACK": "▶",
|
|
21
|
+
"TRIANGLE_RIGHT_WHITE": "▷",
|
|
22
|
+
},
|
|
23
|
+
"PIPE_ASCII": {
|
|
24
|
+
"HORIZONTAL": "-",
|
|
25
|
+
"VERTICAL": "|",
|
|
26
|
+
"TOP_LEFT": "/",
|
|
27
|
+
"TOP_RIGHT": "\\",
|
|
28
|
+
"CROSS": "+",
|
|
29
|
+
},
|
|
30
|
+
"PIPE_EXTENDED": {
|
|
31
|
+
"HORIZONTAL": "─",
|
|
32
|
+
"VERTICAL": "│",
|
|
33
|
+
"BOTTOM_LEFT_TURN": "┌",
|
|
34
|
+
"BOTTOM_RIGHT_TURN": "┐",
|
|
35
|
+
"TOP_LEFT_TURN": "└",
|
|
36
|
+
"TOP_RIGHT_TURN": "┘",
|
|
37
|
+
"T_UP": "┴",
|
|
38
|
+
"T_DOWN": "┬",
|
|
39
|
+
"T_LEFT": "┤",
|
|
40
|
+
"T_RIGHT": "├",
|
|
41
|
+
"CROSS": "┼",
|
|
42
|
+
"HEAVY_HORIZONTAL": "━",
|
|
43
|
+
"HEAVY_VERTICAL": "┃",
|
|
44
|
+
"HEAVY_BOTTOM_LEFT_TURN": "┏",
|
|
45
|
+
"HEAVY_BOTTOM_RIGHT_TURN": "┓",
|
|
46
|
+
"HEAVY_TOP_LEFT_TURN": "┗",
|
|
47
|
+
"HEAVY_TOP_RIGHT_TURN": "┛",
|
|
48
|
+
"HEAVY_T_UP": "┻",
|
|
49
|
+
"HEAVY_T_DOWN": "┳",
|
|
50
|
+
"HEAVY_T_LEFT": "┫",
|
|
51
|
+
"HEAVY_T_RIGHT": "┣",
|
|
52
|
+
"HEAVY_CROSS": "╋",
|
|
53
|
+
"DOUBLE_HORIZONTAL": "═",
|
|
54
|
+
"DOUBLE_VERTICAL": "║",
|
|
55
|
+
"DOUBLE_BOTTOM_LEFT_TURN": "╔",
|
|
56
|
+
"DOUBLE_BOTTOM_RIGHT_TURN": "╗",
|
|
57
|
+
"DOUBLE_TOP_LEFT_TURN": "╚",
|
|
58
|
+
"DOUBLE_TOP_RIGHT_TURN": "╝",
|
|
59
|
+
"DOUBLE_T_UP": "╩",
|
|
60
|
+
"DOUBLE_T_DOWN": "╦",
|
|
61
|
+
"DOUBLE_T_LEFT": "╣",
|
|
62
|
+
"DOUBLE_T_RIGHT": "╠",
|
|
63
|
+
"DOUBLE_CROSS": "╬",
|
|
64
|
+
"ROUNDED_BOTTOM_LEFT_TURN": "╭",
|
|
65
|
+
"ROUNDED_BOTTOM_RIGHT_TURN": "╮",
|
|
66
|
+
"ROUNDED_TOP_LEFT_TURN": "╰",
|
|
67
|
+
"ROUNDED_TOP_RIGHT_TURN": "╯",
|
|
68
|
+
"ROUNDED_T_UP": "┴",
|
|
69
|
+
"ROUNDED_T_DOWN": "┬",
|
|
70
|
+
"ROUNDED_T_LEFT": "┤",
|
|
71
|
+
"ROUNDED_T_RIGHT": "├",
|
|
72
|
+
"ROUNDED_CROSS": "┼",
|
|
73
|
+
},
|
|
74
|
+
"PIPE_BLOCKS": {
|
|
75
|
+
"FULL_BLOCK": "█",
|
|
76
|
+
"LOWER_HALF_BLOCK": "▄",
|
|
77
|
+
"UPPER_HALF_BLOCK": "▀",
|
|
78
|
+
"LEFT_HALF_BLOCK": "▌",
|
|
79
|
+
"RIGHT_HALF_BLOCK": "▐",
|
|
80
|
+
"LOWER_LEFT_QUARTER_BLOCK": "▖",
|
|
81
|
+
"LOWER_RIGHT_QUARTER_BLOCK": "▗",
|
|
82
|
+
"UPPER_LEFT_QUARTER_BLOCK": "▘",
|
|
83
|
+
"UPPER_RIGHT_QUARTER_BLOCK": "▝",
|
|
84
|
+
"LEFT_THREE_QUARTER_BLOCK": "▙",
|
|
85
|
+
"RIGHT_THREE_QUARTER_BLOCK": "▛",
|
|
86
|
+
"LOWER_THREE_QUARTER_BLOCK": "▟",
|
|
87
|
+
"UPPER_THREE_QUARTER_BLOCK": "▜",
|
|
88
|
+
},
|
|
89
|
+
"PIPE_EXTRAS": {
|
|
90
|
+
"X": "╳",
|
|
91
|
+
"HALF_LEFT_HORIZONTAL": "╴",
|
|
92
|
+
"HALF_TOP_VERTICAL": "╵",
|
|
93
|
+
"HALF_RIGHT_HORIZONTAL": "╶",
|
|
94
|
+
"HALF_BOTTOM_VERTICAL": "╷",
|
|
95
|
+
"END_LEFT": "╼",
|
|
96
|
+
"END_TOP": "╽",
|
|
97
|
+
"END_RIGHT": "╾",
|
|
98
|
+
"END_BOTTOM": "╿",
|
|
99
|
+
},
|
|
100
|
+
"DOTS": {
|
|
101
|
+
"ELLIPSE_MIDDLE": "⋯",
|
|
102
|
+
"ELLIPSE_VERTICAL": "⋮",
|
|
103
|
+
"ELLIPSE_DIAGONAL_RIGHT_TO_LEFT": "⋰",
|
|
104
|
+
"ELLIPSE_DIAGONAL_LEFT_TO_RIGHT": "⋱",
|
|
105
|
+
"MIDDOT": "·",
|
|
106
|
+
"BULLET": "•",
|
|
107
|
+
},
|
|
108
|
+
"ARROWS": {
|
|
109
|
+
"UP_ARROW": "↑",
|
|
110
|
+
"DOWN_ARROW": "↓",
|
|
111
|
+
"LEFT_ARROW": "←",
|
|
112
|
+
"RIGHT_ARROW": "→",
|
|
113
|
+
"UP_DOWN_ARROW": "↕",
|
|
114
|
+
"LEFT_RIGHT_ARROW": "↔",
|
|
115
|
+
"NORTH_WEST_ARROW": "↖",
|
|
116
|
+
"NORTH_EAST_ARROW": "↗",
|
|
117
|
+
"SOUTH_EAST_ARROW": "↘",
|
|
118
|
+
"SOUTH_WEST_ARROW": "↙",
|
|
119
|
+
"DOUBLE_UP_ARROW": "⇑",
|
|
120
|
+
"DOUBLE_DOWN_ARROW": "⇓",
|
|
121
|
+
"DOUBLE_LEFT_ARROW": "⇐",
|
|
122
|
+
"DOUBLE_RIGHT_ARROW": "⇒",
|
|
123
|
+
"DOUBLE_LEFT_RIGHT_ARROW": "⇔",
|
|
124
|
+
"DOUBLE_UP_DOWN_ARROW": "⇕",
|
|
125
|
+
},
|
|
126
|
+
"BRAILLE": {i: chr(0x2800 + i) for i in range(1, 256)},
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if __name__ == "__main__":
|
|
130
|
+
for category, chars in CharLib.items():
|
|
131
|
+
print(f"Category: {category}")
|
|
132
|
+
for name, char in chars.items():
|
|
133
|
+
print(f" {name}: {char}")
|
|
134
|
+
print()
|
libascii/color.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class Color:
|
|
2
|
+
def __init__(self, value: str | tuple[int, int, int]) -> None:
|
|
3
|
+
if isinstance(value, tuple):
|
|
4
|
+
if len(value) != 3 or not all(
|
|
5
|
+
isinstance(c, int) and 0 <= c <= 255 for c in value
|
|
6
|
+
):
|
|
7
|
+
msg = "RGB tuple must contain 3 integers in range 0-255"
|
|
8
|
+
raise ValueError(msg)
|
|
9
|
+
r, g, b = value
|
|
10
|
+
self.value = f"#{r:02x}{g:02x}{b:02x}"
|
|
11
|
+
elif value == "":
|
|
12
|
+
self.value = None
|
|
13
|
+
else:
|
|
14
|
+
self.value = value
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def from_hex(cls, hex_str: str) -> "Color":
|
|
18
|
+
if not isinstance(hex_str, str) or not hex_str.startswith("#"):
|
|
19
|
+
msg = "hex string must be in format #RRGGBB"
|
|
20
|
+
raise ValueError(msg)
|
|
21
|
+
if len(hex_str) != 7:
|
|
22
|
+
msg = "hex string must be in format #RRGGBB"
|
|
23
|
+
raise ValueError(msg)
|
|
24
|
+
try:
|
|
25
|
+
int(hex_str[1:], 16)
|
|
26
|
+
except ValueError:
|
|
27
|
+
msg = f"invalid hex string: {hex_str!r}"
|
|
28
|
+
raise ValueError(msg) from None
|
|
29
|
+
return cls(hex_str)
|
|
30
|
+
|
|
31
|
+
def __str__(self) -> str:
|
|
32
|
+
return self.value if self.value is not None else ""
|
libascii/game.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tkinter as tk
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
|
|
6
|
+
from .color import Color
|
|
7
|
+
from .grid import Grid
|
|
8
|
+
from .shapes.shape import Shape
|
|
9
|
+
from ._utils.keymapping import keybinding_to_events
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Game:
|
|
13
|
+
@staticmethod
|
|
14
|
+
def _prevent_selection(event: tk.Event[tk.Misc]) -> str:
|
|
15
|
+
return "break"
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
width: int = 80,
|
|
20
|
+
height: int = 24,
|
|
21
|
+
title: str = "asciilib game",
|
|
22
|
+
fg: Color | None = None,
|
|
23
|
+
bg: Color | None = None,
|
|
24
|
+
filler: str = " ",
|
|
25
|
+
fps: int = 60,
|
|
26
|
+
) -> None:
|
|
27
|
+
self.width = width
|
|
28
|
+
self.height = height
|
|
29
|
+
|
|
30
|
+
self.default_bg = bg if bg is not None else Color("black")
|
|
31
|
+
self.default_fg = fg if fg is not None else Color("white")
|
|
32
|
+
|
|
33
|
+
self.filler = filler
|
|
34
|
+
self.title = title
|
|
35
|
+
|
|
36
|
+
self.grid = Grid(width, height, filler)
|
|
37
|
+
|
|
38
|
+
self._key_down_bindings: list[tuple[str, Callable]] = []
|
|
39
|
+
self._key_up_bindings: list[tuple[str, Callable]] = []
|
|
40
|
+
|
|
41
|
+
self._mouse_down_bindings: list[Callable[[tuple[int, int]], None]] = []
|
|
42
|
+
self._mouse_up_bindings: list[Callable[[tuple[int, int]], None]] = []
|
|
43
|
+
|
|
44
|
+
self._frame_bindings: list[Callable[[], None]] = []
|
|
45
|
+
self._fps = fps
|
|
46
|
+
|
|
47
|
+
self._running = False
|
|
48
|
+
self.root: tk.Tk | None = None
|
|
49
|
+
self.area: tk.Text | None = None
|
|
50
|
+
|
|
51
|
+
def on_key_down(
|
|
52
|
+
self,
|
|
53
|
+
keybinding: str,
|
|
54
|
+
callback: Callable[[tk.Event[tk.Misc]], None],
|
|
55
|
+
) -> None:
|
|
56
|
+
self._key_down_bindings.append((keybinding, callback))
|
|
57
|
+
|
|
58
|
+
def on_key_up(
|
|
59
|
+
self,
|
|
60
|
+
keybinding: str,
|
|
61
|
+
callback: Callable[[tk.Event[tk.Misc]], None],
|
|
62
|
+
) -> None:
|
|
63
|
+
self._key_up_bindings.append((keybinding, callback))
|
|
64
|
+
|
|
65
|
+
def on_mouse_down(
|
|
66
|
+
self,
|
|
67
|
+
callback: Callable[[tuple[int, int]], None],
|
|
68
|
+
) -> None:
|
|
69
|
+
self._mouse_down_bindings.append(callback)
|
|
70
|
+
|
|
71
|
+
def on_mouse_up(
|
|
72
|
+
self,
|
|
73
|
+
callback: Callable[[tuple[int, int]], None],
|
|
74
|
+
) -> None:
|
|
75
|
+
self._mouse_up_bindings.append(callback)
|
|
76
|
+
|
|
77
|
+
def on_frame(self, callback: Callable[[], None]) -> None:
|
|
78
|
+
self._frame_bindings.append(callback)
|
|
79
|
+
|
|
80
|
+
def draw(self, shape: Shape) -> None:
|
|
81
|
+
shape.draw(self.grid)
|
|
82
|
+
|
|
83
|
+
def set(
|
|
84
|
+
self,
|
|
85
|
+
x: int,
|
|
86
|
+
y: int,
|
|
87
|
+
char: str = "",
|
|
88
|
+
color: Color | None = None,
|
|
89
|
+
) -> None:
|
|
90
|
+
self.grid.set(x, y, char, color)
|
|
91
|
+
|
|
92
|
+
def get(self, x: int, y: int) -> tuple[str, Color | None]:
|
|
93
|
+
return self.grid.get(x, y)
|
|
94
|
+
|
|
95
|
+
def clear(self) -> None:
|
|
96
|
+
self.grid.clear()
|
|
97
|
+
|
|
98
|
+
def text(
|
|
99
|
+
self,
|
|
100
|
+
x: int,
|
|
101
|
+
y: int,
|
|
102
|
+
string: str,
|
|
103
|
+
color: Color | None = None,
|
|
104
|
+
) -> None:
|
|
105
|
+
for offset, char in enumerate(string):
|
|
106
|
+
self.grid.set(x + offset, y, char, color)
|
|
107
|
+
|
|
108
|
+
def refresh(self) -> None:
|
|
109
|
+
self._render()
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def fps(self) -> int:
|
|
113
|
+
return self._fps
|
|
114
|
+
|
|
115
|
+
@fps.setter
|
|
116
|
+
def fps(self, value: int) -> None:
|
|
117
|
+
self._fps = value
|
|
118
|
+
|
|
119
|
+
def stop(self) -> None:
|
|
120
|
+
if not self._running:
|
|
121
|
+
return
|
|
122
|
+
self._running = False
|
|
123
|
+
if self.root is not None:
|
|
124
|
+
self.root.quit()
|
|
125
|
+
|
|
126
|
+
def start(self) -> None:
|
|
127
|
+
root = tk.Tk()
|
|
128
|
+
root.title(self.title)
|
|
129
|
+
root.protocol("WM_DELETE_WINDOW", self.stop)
|
|
130
|
+
self.root = root
|
|
131
|
+
|
|
132
|
+
self.area = tk.Text(
|
|
133
|
+
self.root,
|
|
134
|
+
width=self.width,
|
|
135
|
+
height=self.height,
|
|
136
|
+
font=("Courier", 10),
|
|
137
|
+
bg=str(self.default_bg),
|
|
138
|
+
fg=str(self.default_fg),
|
|
139
|
+
padx=0,
|
|
140
|
+
pady=0,
|
|
141
|
+
borderwidth=0,
|
|
142
|
+
highlightthickness=0,
|
|
143
|
+
)
|
|
144
|
+
self.area.pack()
|
|
145
|
+
|
|
146
|
+
self.area.bind("<Button-1>", self._prevent_selection)
|
|
147
|
+
self.area.bind("<B1-Motion>", self._prevent_selection)
|
|
148
|
+
|
|
149
|
+
for keybinding, callback in self._key_down_bindings:
|
|
150
|
+
for event in keybinding_to_events(keybinding, "KeyPress"):
|
|
151
|
+
self.area.bind(event, callback)
|
|
152
|
+
|
|
153
|
+
for keybinding, callback in self._key_up_bindings:
|
|
154
|
+
for event in keybinding_to_events(keybinding, "KeyRelease"):
|
|
155
|
+
self.area.bind(event, callback)
|
|
156
|
+
|
|
157
|
+
def _make_mouse_handler(
|
|
158
|
+
bindings: list[Callable[[tuple[int, int]], None]],
|
|
159
|
+
) -> Callable[[tk.Event[tk.Misc]], None]:
|
|
160
|
+
def handler(event: tk.Event[tk.Misc]) -> None:
|
|
161
|
+
line, col = self.area.index(f"@{event.x},{event.y}").split(".")
|
|
162
|
+
coord = (int(col), int(line) - 1)
|
|
163
|
+
for binding in bindings:
|
|
164
|
+
binding(coord)
|
|
165
|
+
|
|
166
|
+
return handler
|
|
167
|
+
|
|
168
|
+
down_handler = _make_mouse_handler(self._mouse_down_bindings)
|
|
169
|
+
up_handler = _make_mouse_handler(self._mouse_up_bindings)
|
|
170
|
+
self.area.bind("<Button-1>", down_handler)
|
|
171
|
+
self.area.bind("<ButtonRelease-1>", up_handler)
|
|
172
|
+
|
|
173
|
+
self._running = True
|
|
174
|
+
self._render()
|
|
175
|
+
|
|
176
|
+
def _tick() -> None:
|
|
177
|
+
if not self._running:
|
|
178
|
+
return
|
|
179
|
+
for callback in self._frame_bindings:
|
|
180
|
+
callback()
|
|
181
|
+
self.root.after(round(1000 / self._fps), _tick)
|
|
182
|
+
|
|
183
|
+
self.root.after(round(1000 / self._fps), _tick)
|
|
184
|
+
self.root.mainloop()
|
|
185
|
+
|
|
186
|
+
def _render(self) -> None:
|
|
187
|
+
self.area.delete("1.0", tk.END)
|
|
188
|
+
|
|
189
|
+
for y, row in enumerate(self.grid.content):
|
|
190
|
+
self.area.insert(f"{y + 1}.0", "".join(row) + "\n")
|
|
191
|
+
for y, row in enumerate(self.grid.colors):
|
|
192
|
+
for x, color in enumerate(row):
|
|
193
|
+
if color is not None:
|
|
194
|
+
color_str = str(color)
|
|
195
|
+
|
|
196
|
+
if color_str:
|
|
197
|
+
self.area.tag_add(
|
|
198
|
+
color_str, f"{y + 1}.{x}", f"{y + 1}.{x + 1}"
|
|
199
|
+
)
|
|
200
|
+
self.area.tag_config(color_str, foreground=color_str)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if __name__ == "__main__":
|
|
204
|
+
Game().start()
|
libascii/grid.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from libascii.color import Color
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Grid:
|
|
5
|
+
def __init__(self, width: int, height: int, filler: str = " ") -> None:
|
|
6
|
+
self.width = width
|
|
7
|
+
self.height = height
|
|
8
|
+
self.filler = filler
|
|
9
|
+
self.content: list[list[str]] = [
|
|
10
|
+
[filler for _ in range(width)] for _ in range(height)
|
|
11
|
+
]
|
|
12
|
+
self.colors: list[list[Color | None]] = [
|
|
13
|
+
[None for _ in range(width)] for _ in range(height)
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
def in_bounds(self, x: int, y: int) -> bool:
|
|
17
|
+
return 0 <= x < self.width and 0 <= y < self.height
|
|
18
|
+
|
|
19
|
+
def set(
|
|
20
|
+
self,
|
|
21
|
+
x: int,
|
|
22
|
+
y: int,
|
|
23
|
+
char: str = "",
|
|
24
|
+
color: Color | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
self.content[y][x] = char
|
|
27
|
+
self.colors[y][x] = color
|
|
28
|
+
|
|
29
|
+
def set_many(
|
|
30
|
+
self, cells: list[tuple[int, int, str, Color | None]]
|
|
31
|
+
) -> None:
|
|
32
|
+
for x, y, char, color in cells:
|
|
33
|
+
self.set(x, y, char, color)
|
|
34
|
+
|
|
35
|
+
def get(self, x: int, y: int) -> tuple[str, Color | None]:
|
|
36
|
+
return self.content[y][x], self.colors[y][x]
|
|
37
|
+
|
|
38
|
+
def fill(self, char: str = "", color: Color | None = None) -> None:
|
|
39
|
+
for y in range(self.height):
|
|
40
|
+
for x in range(self.width):
|
|
41
|
+
self.content[y][x] = char
|
|
42
|
+
self.colors[y][x] = color
|
|
43
|
+
|
|
44
|
+
def clear(self) -> None:
|
|
45
|
+
self.fill(self.filler, None)
|
libascii/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from ..color import Color
|
|
2
|
+
from ..grid import Grid
|
|
3
|
+
from .shape import Shape
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Ellipse(Shape):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
width: int,
|
|
10
|
+
height: int,
|
|
11
|
+
fill: str = "",
|
|
12
|
+
border: str = "",
|
|
13
|
+
fill_color: Color = Color(""),
|
|
14
|
+
border_color: Color = Color(""),
|
|
15
|
+
border_top: str = "",
|
|
16
|
+
border_left: str = "",
|
|
17
|
+
border_right: str = "",
|
|
18
|
+
border_bottom: str = "",
|
|
19
|
+
) -> None:
|
|
20
|
+
self.width = width
|
|
21
|
+
self.height = height
|
|
22
|
+
self.fill = fill
|
|
23
|
+
self.border = border
|
|
24
|
+
self.fill_color = fill_color
|
|
25
|
+
self.border_color = border_color
|
|
26
|
+
self.border_top = border_top
|
|
27
|
+
self.border_left = border_left
|
|
28
|
+
self.border_right = border_right
|
|
29
|
+
self.border_bottom = border_bottom
|
|
30
|
+
|
|
31
|
+
def draw(self, grid: Grid) -> None:
|
|
32
|
+
if self.width == 0 or self.height == 0:
|
|
33
|
+
return
|
|
34
|
+
cx = (self.width - 1) / 2
|
|
35
|
+
cy = (self.height - 1) / 2
|
|
36
|
+
rx = max((self.width - 1) / 2, 1e-9)
|
|
37
|
+
ry = max((self.height - 1) / 2, 1e-9)
|
|
38
|
+
|
|
39
|
+
def inside(x: int, y: int) -> bool:
|
|
40
|
+
if x < 0 or x >= self.width or y < 0 or y >= self.height:
|
|
41
|
+
return False
|
|
42
|
+
dx = (x - cx) / rx
|
|
43
|
+
dy = (y - cy) / ry
|
|
44
|
+
return dx * dx + dy * dy <= 1
|
|
45
|
+
|
|
46
|
+
for y in range(self.height):
|
|
47
|
+
for x in range(self.width):
|
|
48
|
+
if not inside(x, y):
|
|
49
|
+
continue
|
|
50
|
+
on_border = not (
|
|
51
|
+
inside(x - 1, y)
|
|
52
|
+
and inside(x + 1, y)
|
|
53
|
+
and inside(x, y - 1)
|
|
54
|
+
and inside(x, y + 1)
|
|
55
|
+
)
|
|
56
|
+
if on_border:
|
|
57
|
+
grid.set(x, y, self._border_char(x, y), self.border_color)
|
|
58
|
+
else:
|
|
59
|
+
grid.set(x, y, self.fill, self.fill_color)
|
|
60
|
+
|
|
61
|
+
def _border_char(self, x: int, y: int) -> str:
|
|
62
|
+
if y == 0 and self.border_top:
|
|
63
|
+
return self.border_top
|
|
64
|
+
if y == self.height - 1 and self.border_bottom:
|
|
65
|
+
return self.border_bottom
|
|
66
|
+
if x == 0 and self.border_left:
|
|
67
|
+
return self.border_left
|
|
68
|
+
if x == self.width - 1 and self.border_right:
|
|
69
|
+
return self.border_right
|
|
70
|
+
return self.border
|
libascii/shapes/line.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from ..color import Color
|
|
2
|
+
from ..grid import Grid
|
|
3
|
+
from .shape import Shape
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Line(Shape):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
x1: int,
|
|
10
|
+
y1: int,
|
|
11
|
+
x2: int,
|
|
12
|
+
y2: int,
|
|
13
|
+
char: str = "#",
|
|
14
|
+
color: Color = Color(""),
|
|
15
|
+
) -> None:
|
|
16
|
+
self.x1 = x1
|
|
17
|
+
self.y1 = y1
|
|
18
|
+
self.x2 = x2
|
|
19
|
+
self.y2 = y2
|
|
20
|
+
self.char = char
|
|
21
|
+
self.color = color
|
|
22
|
+
|
|
23
|
+
def draw(self, grid: Grid) -> None:
|
|
24
|
+
x0, y0 = self.x1, self.y1
|
|
25
|
+
x1, y1 = self.x2, self.y2
|
|
26
|
+
dx = abs(x1 - x0)
|
|
27
|
+
dy = abs(y1 - y0)
|
|
28
|
+
sx = 1 if x0 < x1 else -1
|
|
29
|
+
sy = 1 if y0 < y1 else -1
|
|
30
|
+
err = dx - dy
|
|
31
|
+
|
|
32
|
+
while True:
|
|
33
|
+
grid.set(x0, y0, self.char, self.color)
|
|
34
|
+
if x0 == x1 and y0 == y1:
|
|
35
|
+
break
|
|
36
|
+
e2 = 2 * err
|
|
37
|
+
if e2 > -dy:
|
|
38
|
+
err -= dy
|
|
39
|
+
x0 += sx
|
|
40
|
+
if e2 < dx:
|
|
41
|
+
err += dx
|
|
42
|
+
y0 += sy
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from ..color import Color
|
|
2
|
+
from ..grid import Grid
|
|
3
|
+
from .shape import Shape
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Rectangle(Shape):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
width: int,
|
|
10
|
+
height: int,
|
|
11
|
+
fill: str = "",
|
|
12
|
+
border: str = "",
|
|
13
|
+
fill_color: Color = Color(""),
|
|
14
|
+
border_color: Color = Color(""),
|
|
15
|
+
border_top: str = "",
|
|
16
|
+
border_left: str = "",
|
|
17
|
+
border_right: str = "",
|
|
18
|
+
border_bottom: str = "",
|
|
19
|
+
) -> None:
|
|
20
|
+
self.width = width
|
|
21
|
+
self.height = height
|
|
22
|
+
self.fill = fill
|
|
23
|
+
self.border = border
|
|
24
|
+
self.fill_color = fill_color
|
|
25
|
+
self.border_color = border_color
|
|
26
|
+
self.border_top = border_top
|
|
27
|
+
self.border_left = border_left
|
|
28
|
+
self.border_right = border_right
|
|
29
|
+
self.border_bottom = border_bottom
|
|
30
|
+
|
|
31
|
+
def draw(self, grid: Grid) -> None:
|
|
32
|
+
for y in range(self.height):
|
|
33
|
+
for x in range(self.width):
|
|
34
|
+
on_border = (
|
|
35
|
+
x == 0
|
|
36
|
+
or x == self.width - 1
|
|
37
|
+
or y == 0
|
|
38
|
+
or y == self.height - 1
|
|
39
|
+
)
|
|
40
|
+
if on_border:
|
|
41
|
+
grid.set(x, y, self._border_char(x, y), self.border_color)
|
|
42
|
+
else:
|
|
43
|
+
grid.set(x, y, self.fill, self.fill_color)
|
|
44
|
+
|
|
45
|
+
def _border_char(self, x: int, y: int) -> str:
|
|
46
|
+
if y == 0 and self.border_top:
|
|
47
|
+
return self.border_top
|
|
48
|
+
if y == self.height - 1 and self.border_bottom:
|
|
49
|
+
return self.border_bottom
|
|
50
|
+
if x == 0 and self.border_left:
|
|
51
|
+
return self.border_left
|
|
52
|
+
if x == self.width - 1 and self.border_right:
|
|
53
|
+
return self.border_right
|
|
54
|
+
return self.border
|
libascii/shapes/shape.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from ..color import Color
|
|
2
|
+
from ..grid import Grid
|
|
3
|
+
from .shape import Shape
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Triangle(Shape):
|
|
7
|
+
def __init__(
|
|
8
|
+
self,
|
|
9
|
+
width: int,
|
|
10
|
+
height: int,
|
|
11
|
+
fill: str = "",
|
|
12
|
+
border: str = "",
|
|
13
|
+
fill_color: Color = Color(""),
|
|
14
|
+
border_color: Color = Color(""),
|
|
15
|
+
border_left: str = "",
|
|
16
|
+
border_right: str = "",
|
|
17
|
+
border_bottom: str = "",
|
|
18
|
+
) -> None:
|
|
19
|
+
self.width = width
|
|
20
|
+
self.height = height
|
|
21
|
+
self.fill = fill
|
|
22
|
+
self.border = border
|
|
23
|
+
self.fill_color = fill_color
|
|
24
|
+
self.border_color = border_color
|
|
25
|
+
self.border_left = border_left
|
|
26
|
+
self.border_right = border_right
|
|
27
|
+
self.border_bottom = border_bottom
|
|
28
|
+
|
|
29
|
+
def draw(self, grid: Grid) -> None:
|
|
30
|
+
if self.height == 0 or self.width == 0:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
top = (self.width // 2, 0)
|
|
34
|
+
bottom_left = (0, self.height - 1)
|
|
35
|
+
bottom_right = (self.width - 1, self.height - 1)
|
|
36
|
+
|
|
37
|
+
def edge_x(
|
|
38
|
+
a: tuple[int, int],
|
|
39
|
+
b: tuple[int, int],
|
|
40
|
+
y: int,
|
|
41
|
+
) -> float | None:
|
|
42
|
+
(x0, y0), (x1, y1) = a, b
|
|
43
|
+
if y0 == y1 or y < min(y0, y1) or y > max(y0, y1):
|
|
44
|
+
return None
|
|
45
|
+
return x0 + (x1 - x0) * (y - y0) / (y1 - y0)
|
|
46
|
+
|
|
47
|
+
edges = (
|
|
48
|
+
(top, bottom_left),
|
|
49
|
+
(top, bottom_right),
|
|
50
|
+
(bottom_left, bottom_right),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
for y in range(self.height):
|
|
54
|
+
xs = []
|
|
55
|
+
for a, b in edges:
|
|
56
|
+
x = edge_x(a, b, y)
|
|
57
|
+
if x is not None:
|
|
58
|
+
xs.append(x)
|
|
59
|
+
if len(xs) < 2:
|
|
60
|
+
continue
|
|
61
|
+
left = int(round(min(xs)))
|
|
62
|
+
right = int(round(max(xs)))
|
|
63
|
+
for x in range(left, right + 1):
|
|
64
|
+
if x < 0 or x >= self.width:
|
|
65
|
+
continue
|
|
66
|
+
on_border = x == left or x == right or y == self.height - 1
|
|
67
|
+
if on_border:
|
|
68
|
+
grid.set(
|
|
69
|
+
x,
|
|
70
|
+
y,
|
|
71
|
+
self._border_char(x, y, left, right),
|
|
72
|
+
self.border_color,
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
grid.set(x, y, self.fill, self.fill_color)
|
|
76
|
+
|
|
77
|
+
def _border_char(self, x: int, y: int, left: int, right: int) -> str:
|
|
78
|
+
if y == self.height - 1 and self.border_bottom:
|
|
79
|
+
return self.border_bottom
|
|
80
|
+
if x == left and self.border_left:
|
|
81
|
+
return self.border_left
|
|
82
|
+
if x == right and self.border_right:
|
|
83
|
+
return self.border_right
|
|
84
|
+
return self.border
|
libascii/sound.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
import simpleaudio as sa
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Sound:
|
|
7
|
+
def __init__(self, path: str | Path) -> None:
|
|
8
|
+
self.wave_object = sa.WaveObject.from_wave_file(str(path))
|
|
9
|
+
|
|
10
|
+
@classmethod
|
|
11
|
+
def from_wav(cls, path: str | Path) -> "Sound":
|
|
12
|
+
return cls(path)
|
|
13
|
+
|
|
14
|
+
def play(self) -> sa.PlayObject:
|
|
15
|
+
return self.wave_object.play()
|
|
16
|
+
|
|
17
|
+
def stop(self, play_object: sa.PlayObject) -> None:
|
|
18
|
+
play_object.stop()
|
|
19
|
+
|
|
20
|
+
def is_playing(self, play_object: sa.PlayObject) -> bool:
|
|
21
|
+
return play_object.is_playing()
|
|
22
|
+
|
|
23
|
+
def wait_done(self, play_object: sa.PlayObject) -> None:
|
|
24
|
+
play_object.wait_done()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: libascii
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: NNlk05
|
|
6
|
+
License-Expression: MIT OR Zlib
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Natural Language :: English
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.16
|
|
17
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
18
|
+
Classifier: Topic :: Games/Entertainment
|
|
19
|
+
Classifier: Topic :: Games/Entertainment :: Arcade
|
|
20
|
+
Classifier: Topic :: Games/Entertainment :: Board Games
|
|
21
|
+
Classifier: Topic :: Games/Entertainment :: Multi-User Dungeons (MUD)
|
|
22
|
+
Classifier: Topic :: Games/Entertainment :: Puzzle Games
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Dist: simpleaudio>=1.0.4
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
libascii/__init__.py,sha256=MTormQASUYaytNh8YXveW0lGwc4rg3-cyMNKbvYACA8,2276
|
|
2
|
+
libascii/_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
libascii/_utils/keymapping.py,sha256=HtUOTtiEIsqA5X-Tn2jKFFALEeYt3ls8oCcheNSo4-U,2196
|
|
4
|
+
libascii/chars.py,sha256=vWS0higBty76cFiqPJjVw7mrJX_2ONiEKz6jL7QiLDY,4225
|
|
5
|
+
libascii/color.py,sha256=Q0vFwz5xCFbJ4dv4758Qprppir1mCLIcOP-J4Gujnfk,1173
|
|
6
|
+
libascii/game.py,sha256=EKIbBsccpELysKe7IukxMmoW3oam42i4M9VCv9x6nTk,5917
|
|
7
|
+
libascii/grid.py,sha256=G6-RyGUNm4AYpG118BncwMsbiQeTzaT-mGmOJgyXqEg,1357
|
|
8
|
+
libascii/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
+
libascii/shapes/__init__.py,sha256=Q0F6Zv28ECoStbxqqTo_WsxX19q-v1yAiXAN5h-p7A8,141
|
|
10
|
+
libascii/shapes/ellipse.py,sha256=EDozWxDcbutxsZhNc9tmeC15We__JU4oECEsxWWhYGM,2271
|
|
11
|
+
libascii/shapes/line.py,sha256=Ch5oQVlTB02PwefWovoDLaJwkBCIisO3hZFRyDr-K4k,966
|
|
12
|
+
libascii/shapes/rectangle.py,sha256=n_Tia3WYw--qvH4Kix9r-LbZHeC-Rj9lvVpWF327iZg,1711
|
|
13
|
+
libascii/shapes/shape.py,sha256=5gjRUWYCChIc6c6oXdIRnPWp90O6WiigacfMWtr3BOg,160
|
|
14
|
+
libascii/shapes/triangle.py,sha256=1bMBLZmCMwDjFCIAJxyreJTZ64arF2SnUdtesDGbC7I,2624
|
|
15
|
+
libascii/sound.py,sha256=AEGKNLgTt8pHR2c-Ea-HgiB70RqV8kOfPlCqqHL7wYY,636
|
|
16
|
+
libascii-0.1.0.dist-info/licenses/LICENSE,sha256=gvoF8ebg3xSObRCtcOGNXBpA91LQ-gysMpCisjyGlNs,1960
|
|
17
|
+
libascii-0.1.0.dist-info/WHEEL,sha256=MWx7psoF7KNST9G72pGtiBYuaXRXQXBk9pw5R6FzYko,80
|
|
18
|
+
libascii-0.1.0.dist-info/METADATA,sha256=ia1CfLFd6FO8sWUON4_AgFvupAWChGU3ri6lxFAD8X4,1039
|
|
19
|
+
libascii-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
This project is dual-licensed under MIT or zlib.
|
|
2
|
+
|
|
3
|
+
zlib license:
|
|
4
|
+
Copyright (c) 2026 NNlk05
|
|
5
|
+
|
|
6
|
+
This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software.
|
|
7
|
+
|
|
8
|
+
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions:
|
|
9
|
+
|
|
10
|
+
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
|
11
|
+
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
|
12
|
+
3. This notice may not be removed or altered from any source distribution.
|
|
13
|
+
|
|
14
|
+
MIT license:
|
|
15
|
+
Copyright (c) 2026 NNlk05
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|