sonolus.py 0.1.4__py3-none-any.whl → 0.1.5__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.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/backend/finalize.py +18 -10
- sonolus/backend/interpret.py +7 -7
- sonolus/backend/ir.py +24 -0
- sonolus/backend/optimize/__init__.py +0 -0
- sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
- sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
- sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
- sonolus/backend/optimize/dead_code.py +185 -0
- sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
- sonolus/backend/{flow.py → optimize/flow.py} +6 -5
- sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
- sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
- sonolus/backend/optimize/optimize.py +44 -0
- sonolus/backend/{passes.py → optimize/passes.py} +1 -1
- sonolus/backend/optimize/simplify.py +191 -0
- sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
- sonolus/backend/place.py +17 -25
- sonolus/backend/utils.py +10 -0
- sonolus/backend/visitor.py +360 -101
- sonolus/build/compile.py +8 -8
- sonolus/build/engine.py +10 -5
- sonolus/script/archetype.py +419 -137
- sonolus/script/array.py +25 -8
- sonolus/script/array_like.py +297 -0
- sonolus/script/bucket.py +73 -11
- sonolus/script/containers.py +234 -51
- sonolus/script/debug.py +8 -8
- sonolus/script/easing.py +147 -105
- sonolus/script/effect.py +60 -0
- sonolus/script/engine.py +71 -4
- sonolus/script/globals.py +66 -32
- sonolus/script/instruction.py +79 -25
- sonolus/script/internal/builtin_impls.py +138 -27
- sonolus/script/internal/constant.py +139 -0
- sonolus/script/internal/context.py +14 -5
- sonolus/script/internal/dict_impl.py +65 -0
- sonolus/script/internal/generic.py +6 -9
- sonolus/script/internal/impl.py +38 -13
- sonolus/script/internal/introspection.py +5 -2
- sonolus/script/{math.py → internal/math_impls.py} +28 -28
- sonolus/script/internal/native.py +3 -3
- sonolus/script/internal/random.py +67 -0
- sonolus/script/internal/range.py +81 -0
- sonolus/script/internal/transient.py +51 -0
- sonolus/script/internal/tuple_impl.py +113 -0
- sonolus/script/interval.py +234 -16
- sonolus/script/iterator.py +120 -167
- sonolus/script/level.py +24 -0
- sonolus/script/num.py +79 -47
- sonolus/script/options.py +78 -12
- sonolus/script/particle.py +37 -4
- sonolus/script/pointer.py +4 -4
- sonolus/script/print.py +22 -1
- sonolus/script/project.py +8 -0
- sonolus/script/{graphics.py → quad.py} +75 -12
- sonolus/script/record.py +44 -13
- sonolus/script/runtime.py +50 -1
- sonolus/script/sprite.py +197 -112
- sonolus/script/text.py +2 -0
- sonolus/script/timing.py +72 -0
- sonolus/script/transform.py +296 -66
- sonolus/script/ui.py +134 -78
- sonolus/script/values.py +6 -13
- sonolus/script/vec.py +118 -3
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
- sonolus_py-0.1.5.dist-info/RECORD +89 -0
- sonolus/backend/dead_code.py +0 -80
- sonolus/backend/optimize.py +0 -37
- sonolus/backend/simplify.py +0 -47
- sonolus/script/comptime.py +0 -160
- sonolus/script/random.py +0 -14
- sonolus/script/range.py +0 -58
- sonolus_py-0.1.4.dist-info/RECORD +0 -84
- /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
- {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +0 -0
sonolus/script/array.py
CHANGED
|
@@ -5,29 +5,43 @@ from collections.abc import Iterable
|
|
|
5
5
|
from typing import Any, Self, final
|
|
6
6
|
|
|
7
7
|
from sonolus.backend.place import BlockPlace
|
|
8
|
+
from sonolus.script.array_like import ArrayLike
|
|
8
9
|
from sonolus.script.debug import assert_unreachable
|
|
9
10
|
from sonolus.script.internal.context import ctx
|
|
10
11
|
from sonolus.script.internal.error import InternalError
|
|
11
12
|
from sonolus.script.internal.generic import GenericValue
|
|
12
13
|
from sonolus.script.internal.impl import meta_fn, validate_value
|
|
13
14
|
from sonolus.script.internal.value import Value
|
|
14
|
-
from sonolus.script.iterator import ArrayLike
|
|
15
15
|
from sonolus.script.num import Num
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
@final
|
|
19
19
|
class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
20
|
+
"""A fixed size array of values.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
```python
|
|
24
|
+
array_1 = Array(1, 2, 3)
|
|
25
|
+
array_2 = Array[int, 0]()
|
|
26
|
+
```
|
|
27
|
+
"""
|
|
28
|
+
|
|
20
29
|
_value: list[T] | BlockPlace
|
|
21
30
|
|
|
22
31
|
@classmethod
|
|
23
32
|
@meta_fn
|
|
24
33
|
def element_type(cls) -> type[T] | type[Value]:
|
|
25
|
-
|
|
34
|
+
"""Return the type of elements in this array type."""
|
|
35
|
+
return cls.type_var_value(T)
|
|
26
36
|
|
|
27
37
|
@classmethod
|
|
28
38
|
@meta_fn
|
|
29
39
|
def size(cls) -> int:
|
|
30
|
-
|
|
40
|
+
"""Return the size of this array type.
|
|
41
|
+
|
|
42
|
+
On instances, use `len(array)` instead.
|
|
43
|
+
"""
|
|
44
|
+
return cls.type_var_value(Size)
|
|
31
45
|
|
|
32
46
|
def __new__(cls, *args: T) -> Array[T, Any]:
|
|
33
47
|
if cls._type_args_ is None:
|
|
@@ -130,7 +144,7 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
130
144
|
|
|
131
145
|
def _copy_(self) -> Self:
|
|
132
146
|
if ctx():
|
|
133
|
-
place = ctx().alloc(size=self.
|
|
147
|
+
place = ctx().alloc(size=self._size_())
|
|
134
148
|
result: Self = self._from_place_(place)
|
|
135
149
|
result._copy_from_(self)
|
|
136
150
|
return result
|
|
@@ -145,16 +159,17 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
145
159
|
else:
|
|
146
160
|
return cls._with_value([cls.element_type()._alloc_() for _ in range(cls.size())])
|
|
147
161
|
|
|
162
|
+
def __len__(self):
|
|
163
|
+
return self.size()
|
|
164
|
+
|
|
148
165
|
@meta_fn
|
|
149
166
|
def __getitem__(self, index: Num) -> T:
|
|
150
167
|
index: Num = Num._accept_(index)
|
|
151
|
-
if index._is_py_():
|
|
168
|
+
if index._is_py_() and 0 <= index._as_py_() < self.size():
|
|
152
169
|
const_index = index._as_py_()
|
|
153
170
|
if isinstance(const_index, float) and not const_index.is_integer():
|
|
154
171
|
raise ValueError("Array index must be an integer")
|
|
155
172
|
const_index = int(const_index)
|
|
156
|
-
if not 0 <= const_index < self.size():
|
|
157
|
-
raise IndexError("Array index out of range")
|
|
158
173
|
if isinstance(self._value, list):
|
|
159
174
|
if ctx():
|
|
160
175
|
return self._value[const_index]._get_()
|
|
@@ -215,7 +230,9 @@ class Array[T, Size](GenericValue, ArrayLike[T]):
|
|
|
215
230
|
dst._copy_from_(value)
|
|
216
231
|
|
|
217
232
|
def __eq__(self, other):
|
|
218
|
-
if
|
|
233
|
+
if not isinstance(other, ArrayLike):
|
|
234
|
+
return False
|
|
235
|
+
if len(self) != len(other):
|
|
219
236
|
return False
|
|
220
237
|
i = 0
|
|
221
238
|
while i < self.size():
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from collections.abc import Callable, Sequence
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from sonolus.script.iterator import SonolusIterator
|
|
9
|
+
from sonolus.script.num import Num
|
|
10
|
+
from sonolus.script.record import Record
|
|
11
|
+
from sonolus.script.values import copy
|
|
12
|
+
|
|
13
|
+
# Note: we don't use Range in this file because Range itself inherits from ArrayLike
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ArrayLike[T](Sequence, ABC):
|
|
17
|
+
"""Mixin for array-like objects.
|
|
18
|
+
|
|
19
|
+
Inheritors must implement `__len__`, `__getitem__`, and `__setitem__`.
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
```python
|
|
23
|
+
class MyArrayLike[T](Record, ArrayLike[T]):
|
|
24
|
+
def __len__(self) -> int:
|
|
25
|
+
...
|
|
26
|
+
|
|
27
|
+
def __getitem__(self, index: Num) -> T:
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
def __setitem__(self, index: Num, value: T):
|
|
31
|
+
...
|
|
32
|
+
```
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def __len__(self) -> int:
|
|
37
|
+
"""Return the length of the array."""
|
|
38
|
+
|
|
39
|
+
@abstractmethod
|
|
40
|
+
def __getitem__(self, index: Num) -> T:
|
|
41
|
+
"""Return the item at the given index.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
index: The index of the item. Must be an integer between 0 and `len(self) - 1`.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def __setitem__(self, index: Num, value: T):
|
|
49
|
+
"""Set the value of the item at the given index.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
index: The index of the item. Must be an integer between 0 and `len(self) - 1`.
|
|
53
|
+
value: The value to set.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __iter__(self) -> SonolusIterator[T]:
|
|
57
|
+
"""Return an iterator over the array."""
|
|
58
|
+
return _ArrayIterator(0, self)
|
|
59
|
+
|
|
60
|
+
def __contains__(self, value: Any) -> bool:
|
|
61
|
+
"""Return whether any element in the array is equal to the given value.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
value: The value to check for.
|
|
65
|
+
"""
|
|
66
|
+
i = 0
|
|
67
|
+
while i < len(self):
|
|
68
|
+
if self[i] == value:
|
|
69
|
+
return True
|
|
70
|
+
i += 1
|
|
71
|
+
return False
|
|
72
|
+
|
|
73
|
+
def __reversed__(self):
|
|
74
|
+
"""Return a reversed view of the array."""
|
|
75
|
+
return _ArrayReverser(self)
|
|
76
|
+
|
|
77
|
+
def _enumerate_(self, start: Num = 0) -> SonolusIterator[T]:
|
|
78
|
+
return _ArrayEnumerator(0, start, self)
|
|
79
|
+
|
|
80
|
+
def index(self, value: T, start: Num = 0, stop: Num | None = None) -> Num:
|
|
81
|
+
"""Return the index of the value in the array equal to the given value.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
value: The value to search for.
|
|
85
|
+
start: The index to start searching from.
|
|
86
|
+
stop: The index to stop searching at. If `None`, search to the end of the array.
|
|
87
|
+
"""
|
|
88
|
+
if stop is None:
|
|
89
|
+
stop = len(self)
|
|
90
|
+
i = start
|
|
91
|
+
while i < stop:
|
|
92
|
+
if self[i] == value:
|
|
93
|
+
return i
|
|
94
|
+
i += 1
|
|
95
|
+
return -1
|
|
96
|
+
|
|
97
|
+
def count(self, value: T) -> Num:
|
|
98
|
+
"""Return the number of elements in the array equal to the given value.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
value: The value to count.
|
|
102
|
+
"""
|
|
103
|
+
count = 0
|
|
104
|
+
i = 0
|
|
105
|
+
while i < len(self):
|
|
106
|
+
if self[i] == value:
|
|
107
|
+
count += 1
|
|
108
|
+
i += 1
|
|
109
|
+
return count
|
|
110
|
+
|
|
111
|
+
def last_index(self, value: T) -> Num:
|
|
112
|
+
"""Return the last index of the value in the array equal to the given value.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
value: The value to search for.
|
|
116
|
+
"""
|
|
117
|
+
i = len(self) - 1
|
|
118
|
+
while i >= 0:
|
|
119
|
+
if self[i] == value:
|
|
120
|
+
return i
|
|
121
|
+
i -= 1
|
|
122
|
+
return -1
|
|
123
|
+
|
|
124
|
+
def index_of_max(self, *, key: Callable[T, Any] | None = None) -> Num:
|
|
125
|
+
"""Return the index of the maximum value in the array.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
key: A one-argument ordering function to use for comparison like the one used in `max()`.
|
|
129
|
+
"""
|
|
130
|
+
if len(self) == 0:
|
|
131
|
+
return -1
|
|
132
|
+
if key is None:
|
|
133
|
+
key = _identity
|
|
134
|
+
max_index = 0
|
|
135
|
+
i = 1
|
|
136
|
+
while i < len(self):
|
|
137
|
+
if key(self[i]) > key(self[max_index]):
|
|
138
|
+
max_index = i
|
|
139
|
+
i += 1
|
|
140
|
+
return max_index
|
|
141
|
+
|
|
142
|
+
def index_of_min(self, *, key: Callable[T, Any] | None = None) -> Num:
|
|
143
|
+
"""Return the index of the minimum value in the array.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
key: A one-argument ordering function to use for comparison like the one used in `min()`.
|
|
147
|
+
"""
|
|
148
|
+
if len(self) == 0:
|
|
149
|
+
return -1
|
|
150
|
+
if key is None:
|
|
151
|
+
key = _identity
|
|
152
|
+
min_index = 0
|
|
153
|
+
i = 1
|
|
154
|
+
while i < len(self):
|
|
155
|
+
if key(self[i]) < key(self[min_index]):
|
|
156
|
+
min_index = i
|
|
157
|
+
i += 1
|
|
158
|
+
return min_index
|
|
159
|
+
|
|
160
|
+
def _max_(self, key: Callable[T, Any] | None = None) -> T:
|
|
161
|
+
return self[self.index_of_max(key=key)]
|
|
162
|
+
|
|
163
|
+
def _min_(self, key: Callable[T, Any] | None = None) -> T:
|
|
164
|
+
return self[self.index_of_min(key=key)]
|
|
165
|
+
|
|
166
|
+
def swap(self, i: Num, j: Num, /):
|
|
167
|
+
"""Swap the values at the given indices.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
i: The first index.
|
|
171
|
+
j: The second index.
|
|
172
|
+
"""
|
|
173
|
+
temp = copy(self[i])
|
|
174
|
+
self[i] = self[j]
|
|
175
|
+
self[j] = temp
|
|
176
|
+
|
|
177
|
+
def sort(self, *, key: Callable[T, Any] | None = None, reverse: bool = False):
|
|
178
|
+
"""Sort the values in the array in place.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
key: A one-argument ordering function to use for comparison.
|
|
182
|
+
reverse: If `True`, sort in descending order, otherwise sort in ascending order.
|
|
183
|
+
"""
|
|
184
|
+
if len(self) < 15 or key is not None:
|
|
185
|
+
if key is None:
|
|
186
|
+
key = _identity
|
|
187
|
+
_insertion_sort(self, 0, len(self), key, reverse)
|
|
188
|
+
else:
|
|
189
|
+
# Heap sort is unstable, so if there's a key, we can't rely on it
|
|
190
|
+
_heap_sort(self, 0, len(self), reverse)
|
|
191
|
+
|
|
192
|
+
def shuffle(self):
|
|
193
|
+
"""Shuffle the values in the array in place."""
|
|
194
|
+
random.shuffle(self) # type: ignore
|
|
195
|
+
|
|
196
|
+
def reverse(self):
|
|
197
|
+
"""Reverse the values in the array in place."""
|
|
198
|
+
i = 0
|
|
199
|
+
j = len(self) - 1
|
|
200
|
+
while i < j:
|
|
201
|
+
self.swap(i, j)
|
|
202
|
+
i += 1
|
|
203
|
+
j -= 1
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _identity[T](value: T) -> T:
|
|
207
|
+
return value
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _insertion_sort[T](array: ArrayLike[T], start: Num, end: Num, key: Callable[T, Any], reverse: bool):
|
|
211
|
+
i = start + 1
|
|
212
|
+
if reverse:
|
|
213
|
+
while i < end:
|
|
214
|
+
j = i
|
|
215
|
+
while j > start and key(array[j - 1]) < key(array[j]):
|
|
216
|
+
array.swap(j - 1, j)
|
|
217
|
+
j -= 1
|
|
218
|
+
i += 1
|
|
219
|
+
else:
|
|
220
|
+
while i < end:
|
|
221
|
+
j = i
|
|
222
|
+
while j > start and key(array[j - 1]) > key(array[j]):
|
|
223
|
+
array.swap(j - 1, j)
|
|
224
|
+
j -= 1
|
|
225
|
+
i += 1
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _heapify[T](array: ArrayLike[T], end: Num, index: Num, reverse: bool):
|
|
229
|
+
while True:
|
|
230
|
+
left = index * 2 + 1
|
|
231
|
+
right = left + 1
|
|
232
|
+
largest = index
|
|
233
|
+
if left < end and (array[left] > array[largest]) != reverse:
|
|
234
|
+
largest = left
|
|
235
|
+
if right < end and (array[right] > array[largest]) != reverse:
|
|
236
|
+
largest = right
|
|
237
|
+
if largest == index:
|
|
238
|
+
break
|
|
239
|
+
array.swap(index, largest)
|
|
240
|
+
index = largest
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _heap_sort[T](array: ArrayLike[T], start: Num, end: Num, reverse: bool):
|
|
244
|
+
i = end // 2 - 1
|
|
245
|
+
while i >= start:
|
|
246
|
+
_heapify(array, end, i, reverse)
|
|
247
|
+
i -= 1
|
|
248
|
+
i = end - 1
|
|
249
|
+
while i > start:
|
|
250
|
+
array.swap(start, i)
|
|
251
|
+
_heapify(array, i, start, reverse)
|
|
252
|
+
i -= 1
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class _ArrayIterator[V: ArrayLike](Record, SonolusIterator):
|
|
256
|
+
i: int
|
|
257
|
+
array: V
|
|
258
|
+
|
|
259
|
+
def has_next(self) -> bool:
|
|
260
|
+
return self.i < len(self.array)
|
|
261
|
+
|
|
262
|
+
def get(self) -> V:
|
|
263
|
+
return self.array[self.i]
|
|
264
|
+
|
|
265
|
+
def advance(self):
|
|
266
|
+
self.i += 1
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class _ArrayReverser[V: ArrayLike](Record, ArrayLike):
|
|
270
|
+
array: V
|
|
271
|
+
|
|
272
|
+
def __len__(self) -> int:
|
|
273
|
+
return len(self.array)
|
|
274
|
+
|
|
275
|
+
def __getitem__(self, index: Num) -> V:
|
|
276
|
+
return self.array[len(self) - 1 - index]
|
|
277
|
+
|
|
278
|
+
def __setitem__(self, index: Num, value: V):
|
|
279
|
+
self.array[len(self) - 1 - index] = value
|
|
280
|
+
|
|
281
|
+
def reversed(self) -> ArrayLike[V]:
|
|
282
|
+
return self.array
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class _ArrayEnumerator[V: ArrayLike](Record, SonolusIterator):
|
|
286
|
+
i: int
|
|
287
|
+
offset: int
|
|
288
|
+
array: V
|
|
289
|
+
|
|
290
|
+
def has_next(self) -> bool:
|
|
291
|
+
return self.i < len(self.array)
|
|
292
|
+
|
|
293
|
+
def get(self) -> tuple[int, Any]:
|
|
294
|
+
return self.i + self.offset, self.array[self.i]
|
|
295
|
+
|
|
296
|
+
def advance(self):
|
|
297
|
+
self.i += 1
|
sonolus/script/bucket.py
CHANGED
|
@@ -11,15 +11,28 @@ from sonolus.script.internal.impl import meta_fn
|
|
|
11
11
|
from sonolus.script.internal.introspection import get_field_specifiers
|
|
12
12
|
from sonolus.script.internal.native import native_function
|
|
13
13
|
from sonolus.script.interval import Interval
|
|
14
|
-
from sonolus.script.pointer import
|
|
14
|
+
from sonolus.script.pointer import _deref
|
|
15
15
|
from sonolus.script.record import Record
|
|
16
16
|
from sonolus.script.sprite import Sprite
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class JudgmentWindow(Record):
|
|
20
|
+
"""The window for judging the accuracy of a hit.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
```
|
|
24
|
+
JudgmentWindow(perfect: Interval, great: Interval, good: Interval)
|
|
25
|
+
```
|
|
26
|
+
"""
|
|
27
|
+
|
|
20
28
|
perfect: Interval
|
|
29
|
+
"""Interval for a perfect hit."""
|
|
30
|
+
|
|
21
31
|
great: Interval
|
|
32
|
+
"""Interval for a great hit."""
|
|
33
|
+
|
|
22
34
|
good: Interval
|
|
35
|
+
"""Interval for a good hit."""
|
|
23
36
|
|
|
24
37
|
def update(
|
|
25
38
|
self,
|
|
@@ -27,6 +40,13 @@ class JudgmentWindow(Record):
|
|
|
27
40
|
great: Interval | None = None,
|
|
28
41
|
good: Interval | None = None,
|
|
29
42
|
):
|
|
43
|
+
"""Update the window with the given intervals.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
perfect: The interval for a perfect hit.
|
|
47
|
+
great: The interval for a great hit.
|
|
48
|
+
good: The interval for a good hit.
|
|
49
|
+
"""
|
|
30
50
|
if perfect is not None:
|
|
31
51
|
self.perfect = perfect
|
|
32
52
|
if great is not None:
|
|
@@ -35,6 +55,15 @@ class JudgmentWindow(Record):
|
|
|
35
55
|
self.good = good
|
|
36
56
|
|
|
37
57
|
def judge(self, actual: float, target: float) -> Judgment:
|
|
58
|
+
"""Judge the accuracy of a hit.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
actual: The actual time of the hit.
|
|
62
|
+
target: The target time of the hit.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
The judgment of the hit.
|
|
66
|
+
"""
|
|
38
67
|
return _judge(
|
|
39
68
|
actual,
|
|
40
69
|
target,
|
|
@@ -44,6 +73,7 @@ class JudgmentWindow(Record):
|
|
|
44
73
|
)
|
|
45
74
|
|
|
46
75
|
def __mul__(self, other: float | int) -> JudgmentWindow:
|
|
76
|
+
"""Multiply the intervals by a scalar."""
|
|
47
77
|
return JudgmentWindow(
|
|
48
78
|
self.perfect * other,
|
|
49
79
|
self.great * other,
|
|
@@ -52,6 +82,8 @@ class JudgmentWindow(Record):
|
|
|
52
82
|
|
|
53
83
|
|
|
54
84
|
class Judgment(IntEnum):
|
|
85
|
+
"""The judgment of a hit."""
|
|
86
|
+
|
|
55
87
|
MISS = 0
|
|
56
88
|
PERFECT = 1
|
|
57
89
|
GREAT = 2
|
|
@@ -80,18 +112,26 @@ def _judge(
|
|
|
80
112
|
|
|
81
113
|
|
|
82
114
|
class Bucket(Record):
|
|
115
|
+
"""A bucket for entity judgment results.
|
|
116
|
+
|
|
117
|
+
Usage:
|
|
118
|
+
```Bucket(id: int)```
|
|
119
|
+
"""
|
|
120
|
+
|
|
83
121
|
id: int
|
|
122
|
+
"""Bucket ID."""
|
|
84
123
|
|
|
85
124
|
@property
|
|
86
125
|
@meta_fn
|
|
87
126
|
def window(self) -> JudgmentWindow:
|
|
127
|
+
"""The judgment window of the bucket."""
|
|
88
128
|
if not ctx():
|
|
89
129
|
raise RuntimeError("Bucket window access outside of compilation")
|
|
90
130
|
match ctx().global_state.mode:
|
|
91
131
|
case Mode.PLAY:
|
|
92
|
-
return
|
|
132
|
+
return _deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
|
|
93
133
|
case Mode.WATCH:
|
|
94
|
-
return
|
|
134
|
+
return _deref(ctx().blocks.LevelBucket, self.id * JudgmentWindow._size_(), JudgmentWindow)
|
|
95
135
|
case _:
|
|
96
136
|
raise RuntimeError("Invalid mode for bucket window access")
|
|
97
137
|
|
|
@@ -104,7 +144,7 @@ class Bucket(Record):
|
|
|
104
144
|
|
|
105
145
|
|
|
106
146
|
@dataclass
|
|
107
|
-
class
|
|
147
|
+
class _BucketSprite:
|
|
108
148
|
id: int
|
|
109
149
|
fallback_id: int | None
|
|
110
150
|
x: int
|
|
@@ -128,8 +168,8 @@ class BucketSprite:
|
|
|
128
168
|
|
|
129
169
|
|
|
130
170
|
@dataclass
|
|
131
|
-
class
|
|
132
|
-
sprites: list[
|
|
171
|
+
class _BucketInfo:
|
|
172
|
+
sprites: list[_BucketSprite]
|
|
133
173
|
unit: str | None = None
|
|
134
174
|
|
|
135
175
|
def to_dict(self):
|
|
@@ -150,12 +190,14 @@ def bucket_sprite(
|
|
|
150
190
|
w: int,
|
|
151
191
|
h: int,
|
|
152
192
|
rotation: float = 0,
|
|
153
|
-
) ->
|
|
154
|
-
|
|
193
|
+
) -> _BucketSprite:
|
|
194
|
+
"""Define a sprite for a bucket."""
|
|
195
|
+
return _BucketSprite(sprite.id, fallback_sprite.id if fallback_sprite else None, x, y, w, h, rotation)
|
|
155
196
|
|
|
156
197
|
|
|
157
|
-
def bucket(*, sprites: list[
|
|
158
|
-
|
|
198
|
+
def bucket(*, sprites: list[_BucketSprite], unit: str | None = None) -> Any:
|
|
199
|
+
"""Define a bucket with the given sprites and unit."""
|
|
200
|
+
return _BucketInfo(sprites, unit)
|
|
159
201
|
|
|
160
202
|
|
|
161
203
|
type Buckets = NewType("Buckets", Any)
|
|
@@ -163,6 +205,26 @@ type Buckets = NewType("Buckets", Any)
|
|
|
163
205
|
|
|
164
206
|
@dataclass_transform()
|
|
165
207
|
def buckets[T](cls: type[T]) -> T | Buckets:
|
|
208
|
+
"""Decorator to define a buckets class.
|
|
209
|
+
|
|
210
|
+
Usage:
|
|
211
|
+
```python
|
|
212
|
+
@buckets
|
|
213
|
+
class Buckets:
|
|
214
|
+
note: Bucket = bucket(
|
|
215
|
+
sprites=[
|
|
216
|
+
bucket_sprite(
|
|
217
|
+
sprite=Skin.note,
|
|
218
|
+
x=0,
|
|
219
|
+
y=0,
|
|
220
|
+
w=2,
|
|
221
|
+
h=2,
|
|
222
|
+
)
|
|
223
|
+
],
|
|
224
|
+
unit=StandardText.MILLISECOND_UNIT,
|
|
225
|
+
)
|
|
226
|
+
```
|
|
227
|
+
"""
|
|
166
228
|
if len(cls.__bases__) != 1:
|
|
167
229
|
raise ValueError("Buckets class must not inherit from any class (except object)")
|
|
168
230
|
instance = cls()
|
|
@@ -174,7 +236,7 @@ def buckets[T](cls: type[T]) -> T | Buckets:
|
|
|
174
236
|
annotation_values = annotation.__metadata__
|
|
175
237
|
if annotation_type is not Bucket:
|
|
176
238
|
raise TypeError(f"Invalid annotation for buckets: {annotation}, expected annotation of type Bucket")
|
|
177
|
-
if len(annotation_values) != 1 or not isinstance(annotation_values[0],
|
|
239
|
+
if len(annotation_values) != 1 or not isinstance(annotation_values[0], _BucketInfo):
|
|
178
240
|
raise TypeError(
|
|
179
241
|
f"Invalid annotation for buckets: {annotation}, expected a single BucketInfo annotation value"
|
|
180
242
|
)
|