koro 2.0.0rc2__py3-none-any.whl → 2.0.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
koro/stage/part.py CHANGED
@@ -1,1704 +1,1754 @@
1
- from __future__ import annotations
2
-
3
- from abc import ABC, abstractmethod
4
- from collections.abc import Iterable, MutableSequence
5
- from enum import Enum, unique, Flag
6
- from sys import maxsize
7
- from typing import Any, Final, Iterator, Literal, Self, SupportsIndex, overload
8
-
9
- from .model import DecorationModel, DeviceModel, PartModel
10
-
11
-
12
- __all__ = [
13
- "Ant",
14
- "BasePart",
15
- "BlinkingTile",
16
- "Bumper",
17
- "Cannon",
18
- "ConveyorBelt",
19
- "DashTunnel",
20
- "Drawbridge",
21
- "Fan",
22
- "FixedSpeedDevice",
23
- "Gear",
24
- "Goal",
25
- "GreenCrystal",
26
- "KororinCapsule",
27
- "Magnet",
28
- "MagnetSegment",
29
- "MagnifyingGlass",
30
- "MelodyTile",
31
- "MovingCurve",
32
- "MovingTile",
33
- "Part",
34
- "Press",
35
- "ProgressMarker",
36
- "Punch",
37
- "Scissors",
38
- "SeesawBlock",
39
- "SizeTunnel",
40
- "SlidingTile",
41
- "Spring",
42
- "Start",
43
- "Thorn",
44
- "TimedDevice",
45
- "ToyTrain",
46
- "TrainTrack",
47
- "Turntable",
48
- "UpsideDownBall",
49
- "UpsideDownStageDevice",
50
- "Walls",
51
- "Warp",
52
- ]
53
-
54
-
55
- class BasePart(ABC):
56
- """Base class for all level elements"""
57
-
58
- __match_args__ = ("x_pos", "y_pos", "z_pos", "x_rot", "y_rot", "z_rot")
59
- __slots__ = ("_x_pos", "_x_rot", "_y_pos", "_y_rot", "_z_pos", "_z_rot")
60
-
61
- _x_pos: float
62
- _x_rot: float
63
- _y_pos: float
64
- _y_rot: float
65
- _z_pos: float
66
- _z_rot: float
67
-
68
- def __init__(
69
- self,
70
- x_pos: float,
71
- y_pos: float,
72
- z_pos: float,
73
- x_rot: float,
74
- y_rot: float,
75
- z_rot: float,
76
- ) -> None:
77
- self.x_pos = x_pos
78
- self.x_rot = x_rot
79
- self.y_pos = y_pos
80
- self.y_rot = y_rot
81
- self.z_pos = z_pos
82
- self.z_rot = z_rot
83
-
84
- @property
85
- @abstractmethod
86
- def cost(self) -> int:
87
- """The number of kororin points that this part costs to place."""
88
-
89
- def __repr__(self) -> str:
90
- return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
91
-
92
- @property
93
- def x_pos(self) -> float:
94
- """Positive is right, negative is left"""
95
- return self._x_pos
96
-
97
- @x_pos.setter
98
- def x_pos(self, value: float, /) -> None:
99
- self._x_pos = value
100
-
101
- @property
102
- def x_rot(self) -> float:
103
- """Represented in degrees from 0 to 360
104
- Positive turns top face front, negative turns top face back
105
- """
106
- return self._x_rot
107
-
108
- @x_rot.setter
109
- def x_rot(self, value: float, /) -> None:
110
- self._x_rot = value % 360
111
-
112
- @property
113
- def y_pos(self) -> float:
114
- """Positive is up, negative is down"""
115
- return self._y_pos
116
-
117
- @y_pos.setter
118
- def y_pos(self, value: float, /) -> None:
119
- self._y_pos = value
120
-
121
- @property
122
- def y_rot(self) -> float:
123
- """Represented in degrees from 0 to 360
124
- Positive turns front face right, negative turns front face left
125
- """
126
- return self._y_rot
127
-
128
- @y_rot.setter
129
- def y_rot(self, value: float, /) -> None:
130
- self._y_rot = value % 360
131
-
132
- @property
133
- def z_pos(self) -> float:
134
- """Positive is front, negative is back"""
135
- return self._z_pos
136
-
137
- @z_pos.setter
138
- def z_pos(self, value: float, /) -> None:
139
- self._z_pos = value
140
-
141
- @property
142
- def z_rot(self) -> float:
143
- """Represented in degrees from 0 to 360
144
- Positive turns top face left, negative turns top face right
145
- """
146
- return self._z_rot
147
-
148
- @z_rot.setter
149
- def z_rot(self, value: float, /) -> None:
150
- self._z_rot = value % 360
151
-
152
-
153
- class Part(BasePart):
154
- """(Usually) static model that has no behavior other than being solid."""
155
-
156
- __slots__ = ("_shape",)
157
-
158
- _shape: PartModel | DecorationModel
159
-
160
- def __init__(
161
- self,
162
- x_pos: float,
163
- y_pos: float,
164
- z_pos: float,
165
- x_rot: float,
166
- y_rot: float,
167
- z_rot: float,
168
- *,
169
- shape: PartModel | DecorationModel,
170
- ) -> None:
171
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
172
- self.shape = shape
173
-
174
- @property
175
- def cost(self) -> Literal[10, 15, 20]:
176
- if isinstance(self.shape, DecorationModel):
177
- return 20
178
- elif self.shape in frozenset(
179
- {
180
- PartModel.MagmaTile,
181
- PartModel.SlipperyTile,
182
- PartModel.StickyTile,
183
- PartModel.InvisibleTile,
184
- }
185
- ):
186
- return 15
187
- else:
188
- return 10
189
-
190
- @property
191
- def shape(self) -> PartModel | DecorationModel:
192
- return self._shape
193
-
194
- @shape.setter
195
- def shape(self, value: PartModel | DecorationModel, /) -> None:
196
- self._shape = value
197
-
198
- def __repr__(self) -> str:
199
- return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
200
-
201
-
202
- class Start(BasePart):
203
- __slots__ = ()
204
-
205
- @property
206
- def cost(self) -> Literal[0]:
207
- return 0
208
-
209
-
210
- class Goal(BasePart):
211
- __slots__ = ()
212
-
213
- @property
214
- def cost(self) -> Literal[0]:
215
- return 0
216
-
217
-
218
- class ProgressMarker(BasePart):
219
- """Either a crystal (when progress is odd) or a respawn (when progress is even)"""
220
-
221
- __slots__ = ("_progress",)
222
-
223
- _progress: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
224
-
225
- def __init__(
226
- self,
227
- x_pos: float,
228
- y_pos: float,
229
- z_pos: float,
230
- x_rot: float,
231
- y_rot: float,
232
- z_rot: float,
233
- *,
234
- progress: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
235
- ) -> None:
236
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
237
- self.progress = progress
238
-
239
- @property
240
- def cost(self) -> Literal[15, 0]:
241
- return 15 if self.progress & 1 else 0
242
-
243
- @property
244
- def progress(self) -> Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
245
- """The number displayed above the object. Controls which crystals are required to enable a respawn."""
246
- return self._progress
247
-
248
- @progress.setter
249
- def progress(self, value: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], /):
250
- self._progress = value
251
-
252
- def __repr__(self) -> str:
253
- return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, progress={self.progress!r})"
254
-
255
-
256
- @unique
257
- class Speed(Enum):
258
- SLOW = 55
259
- NORMAL = 39
260
- FAST = 23
261
-
262
-
263
- @unique
264
- class Walls(Flag):
265
- BACK = 8
266
- FRONT = 2
267
- LEFT = 4
268
- RIGHT = 1
269
-
270
-
271
- class MovingTile(BasePart):
272
- """A part that moves in a straight line."""
273
-
274
- __slots__ = (
275
- "_dest_x",
276
- "_dest_y",
277
- "_dest_z",
278
- "_shape",
279
- "_speed",
280
- "_switch",
281
- "_walls",
282
- )
283
-
284
- _dest_x: float
285
- _dest_y: float
286
- _dest_z: float
287
- _shape: Literal[
288
- PartModel.Tile10x10,
289
- PartModel.Tile20x20,
290
- PartModel.TileA30x30,
291
- PartModel.TileA30x90,
292
- PartModel.Tile90x90,
293
- PartModel.HoleB90x90,
294
- PartModel.FunnelPipe,
295
- PartModel.StraightPipe,
296
- ]
297
- _speed: float
298
- _switch: bool
299
- _walls: Walls
300
-
301
- def __init__(
302
- self,
303
- x_pos: float,
304
- y_pos: float,
305
- z_pos: float,
306
- x_rot: float,
307
- y_rot: float,
308
- z_rot: float,
309
- *,
310
- dest_x: float,
311
- dest_y: float,
312
- dest_z: float,
313
- shape: Literal[
314
- PartModel.Tile10x10,
315
- PartModel.Tile20x20,
316
- PartModel.TileA30x30,
317
- PartModel.TileA30x90,
318
- PartModel.Tile90x90,
319
- PartModel.HoleB90x90,
320
- PartModel.FunnelPipe,
321
- PartModel.StraightPipe,
322
- ],
323
- speed: Speed | float = Speed.NORMAL,
324
- switch: bool = False,
325
- walls: Walls = Walls(0),
326
- ) -> None:
327
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
328
- self.dest_x = dest_x
329
- self.dest_y = dest_y
330
- self.dest_z = dest_z
331
- self.shape = shape
332
- self.speed = speed # type: ignore[assignment]
333
- self.switch = switch
334
- self.walls = walls
335
-
336
- @property
337
- def cost(self) -> Literal[20, 70, 35, 40, 50, 65]:
338
- match self.shape:
339
- case PartModel.Tile10x10:
340
- return 20
341
- case PartModel.Tile20x20 | PartModel.TileA30x30:
342
- return 70
343
- case PartModel.TileA30x90:
344
- return 35
345
- case PartModel.Tile90x90 | PartModel.HoleB90x90:
346
- return 40
347
- case PartModel.FunnelPipe:
348
- return 50
349
- case PartModel.StraightPipe:
350
- return 65
351
-
352
- @property
353
- def dest_x(self) -> float:
354
- """Positive is right, negative is left"""
355
- return self._dest_x
356
-
357
- @dest_x.setter
358
- def dest_x(self, value: float, /) -> None:
359
- self._dest_x = value
360
-
361
- @property
362
- def dest_y(self) -> float:
363
- return self._dest_y
364
-
365
- @dest_y.setter
366
- def dest_y(self, value: float, /) -> None:
367
- """Positive is up, negative is down"""
368
- self._dest_y = value
369
-
370
- @property
371
- def dest_z(self) -> float:
372
- """Positive is front, negative is back"""
373
- return self._dest_z
374
-
375
- @dest_z.setter
376
- def dest_z(self, value: float, /) -> None:
377
- self._dest_z = value
378
-
379
- def __repr__(self) -> str:
380
- output: Final[list[str]] = [
381
- f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, dest_x={self.dest_x!r}, dest_y={self.dest_y!r}, dest_z={self.dest_z!r}, shape={self.shape!r}"
382
- ]
383
- if self.speed != 1.0:
384
- output.append(f", speed={self.speed!r}")
385
- if self.switch:
386
- output.append(f", switch={self.switch!r}")
387
- if self.walls:
388
- output.append(f", walls={self.walls!r}")
389
- output.append(")")
390
- return "".join(output)
391
-
392
- @property
393
- def shape(
394
- self,
395
- ) -> Literal[
396
- PartModel.Tile10x10,
397
- PartModel.Tile20x20,
398
- PartModel.TileA30x30,
399
- PartModel.TileA30x90,
400
- PartModel.Tile90x90,
401
- PartModel.HoleB90x90,
402
- PartModel.FunnelPipe,
403
- PartModel.StraightPipe,
404
- ]:
405
- """The appearance of this moving tile."""
406
- return self._shape
407
-
408
- @shape.setter
409
- def shape(
410
- self,
411
- value: Literal[
412
- PartModel.Tile10x10,
413
- PartModel.Tile20x20,
414
- PartModel.TileA30x30,
415
- PartModel.TileA30x90,
416
- PartModel.Tile90x90,
417
- PartModel.HoleB90x90,
418
- PartModel.FunnelPipe,
419
- PartModel.StraightPipe,
420
- ],
421
- /,
422
- ) -> None:
423
- if (
424
- value in frozenset({PartModel.FunnelPipe, PartModel.StraightPipe})
425
- and self.switch
426
- ):
427
- raise ValueError("Moving pipes cannot be switches")
428
- elif (
429
- value not in frozenset({PartModel.Tile20x20, PartModel.TileA30x30})
430
- and self.walls
431
- ):
432
- raise ValueError("Invalid shape for wall attachment")
433
- else:
434
- self._shape = value
435
-
436
- @property
437
- def speed(self) -> float:
438
- return self._speed
439
-
440
- @speed.setter
441
- def speed(self, value: Speed | float, /) -> None:
442
- match value:
443
- case Speed.SLOW:
444
- self.speed = 0.5
445
- case Speed.NORMAL:
446
- self.speed = 1.0
447
- case Speed.FAST:
448
- self.speed = 1.5
449
- case _:
450
- self._speed = value
451
-
452
- @property
453
- def switch(self) -> bool:
454
- """Whether this moving tile must be touched in order to move."""
455
- return self._switch
456
-
457
- @switch.setter
458
- def switch(self, value: bool, /) -> None:
459
- if self.shape in frozenset({PartModel.FunnelPipe, PartModel.StraightPipe}):
460
- raise ValueError("Moving pipes cannot be switches")
461
- else:
462
- self._switch = value
463
-
464
- @property
465
- def walls(self) -> Walls:
466
- return self._walls
467
-
468
- @walls.setter
469
- def walls(self, value: Walls, /) -> None:
470
- if self.shape in frozenset({PartModel.Tile20x20, PartModel.TileA30x30}):
471
- self._walls = value
472
- else:
473
- raise ValueError("Invalid shape for wall attachment")
474
-
475
-
476
- class FixedSpeedDevice(BasePart, ABC):
477
- """Device template for devices with speeds that can be set to Slow, Normal, or Fast"""
478
-
479
- __slots__ = ("_speed",)
480
-
481
- _speed: Speed
482
-
483
- def __init__(
484
- self,
485
- x_pos: float,
486
- y_pos: float,
487
- z_pos: float,
488
- x_rot: float,
489
- y_rot: float,
490
- z_rot: float,
491
- *,
492
- speed: Speed = Speed.NORMAL,
493
- ) -> None:
494
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
495
- self.speed = speed
496
-
497
- def __repr__(self) -> str:
498
- return (
499
- f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, speed={self.speed!r})"
500
- if self.speed != Speed.NORMAL
501
- else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
502
- )
503
-
504
- @property
505
- def speed(self) -> Speed:
506
- return self._speed
507
-
508
- @speed.setter
509
- def speed(self, value: Speed, /) -> None:
510
- self._speed = value
511
-
512
-
513
- class MovingCurve(FixedSpeedDevice):
514
- __slots__ = ("_shape",)
515
-
516
- _shape: Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL]
517
-
518
- def __init__(
519
- self,
520
- x_pos: float,
521
- y_pos: float,
522
- z_pos: float,
523
- x_rot: float,
524
- y_rot: float,
525
- z_rot: float,
526
- *,
527
- shape: Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL],
528
- speed: Speed = Speed.NORMAL,
529
- ) -> None:
530
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot, speed=speed)
531
- self.shape = shape
532
-
533
- @property
534
- def cost(self) -> Literal[25]:
535
- return 25
536
-
537
- def __repr__(self) -> str:
538
- return (
539
- f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r}, speed={self.speed!r})"
540
- if self.speed != Speed.NORMAL
541
- else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
542
- )
543
-
544
- @property
545
- def shape(self) -> Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL]:
546
- return self._shape
547
-
548
- @shape.setter
549
- def shape(
550
- self, value: Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL], /
551
- ) -> None:
552
- self._shape = value
553
-
554
-
555
- class SlidingTile(BasePart):
556
- __slots__ = ()
557
-
558
- @property
559
- def cost(self) -> Literal[100]:
560
- return 100
561
-
562
-
563
- class ConveyorBelt(BasePart):
564
- __slots__ = ("_reversing",)
565
-
566
- _reversing: bool
567
-
568
- def __init__(
569
- self,
570
- x_pos: float,
571
- y_pos: float,
572
- z_pos: float,
573
- x_rot: float,
574
- y_rot: float,
575
- z_rot: float,
576
- *,
577
- reversing: bool = False,
578
- ) -> None:
579
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
580
- self.reversing = reversing
581
-
582
- @property
583
- def cost(self) -> Literal[60]:
584
- return 60
585
-
586
- def __repr__(self) -> str:
587
- return (
588
- f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, reversing={self.reversing!r})"
589
- if self.reversing
590
- else super().__repr__()
591
- )
592
-
593
- @property
594
- def reversing(self) -> bool:
595
- return self._reversing
596
-
597
- @reversing.setter
598
- def reversing(self, value: bool, /) -> None:
599
- self._reversing = value
600
-
601
-
602
- class MagnetSegment(BasePart):
603
- __slots__ = ("_shape",)
604
-
605
- _shape: Literal[
606
- DeviceModel.EndMagnet,
607
- DeviceModel.StraightMagnet,
608
- DeviceModel.CurveMagnetL,
609
- DeviceModel.CurveMagnetS,
610
- ]
611
-
612
- def __init__(
613
- self,
614
- x_pos: float,
615
- y_pos: float,
616
- z_pos: float,
617
- x_rot: float,
618
- y_rot: float,
619
- z_rot: float,
620
- *,
621
- shape: Literal[
622
- DeviceModel.EndMagnet,
623
- DeviceModel.StraightMagnet,
624
- DeviceModel.CurveMagnetL,
625
- DeviceModel.CurveMagnetS,
626
- ],
627
- ) -> None:
628
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
629
- self.shape = shape
630
-
631
- @property
632
- def cost(self) -> Literal[15]:
633
- return 15
634
-
635
- def __repr__(self) -> str:
636
- return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
637
-
638
- @property
639
- def shape(self) -> Literal[
640
- DeviceModel.EndMagnet,
641
- DeviceModel.StraightMagnet,
642
- DeviceModel.CurveMagnetL,
643
- DeviceModel.CurveMagnetS,
644
- ]:
645
- return self._shape
646
-
647
- @shape.setter
648
- def shape(
649
- self,
650
- value: Literal[
651
- DeviceModel.EndMagnet,
652
- DeviceModel.StraightMagnet,
653
- DeviceModel.CurveMagnetL,
654
- DeviceModel.CurveMagnetS,
655
- ],
656
- /,
657
- ) -> None:
658
- self._shape = value
659
-
660
-
661
- class Magnet(BasePart, MutableSequence[MagnetSegment]):
662
- __slots__ = ("_segments",)
663
-
664
- _segments: list[MagnetSegment]
665
-
666
- def __init__(self, iterable: Iterable[MagnetSegment] = (), /) -> None:
667
- self._segments = list(iterable)
668
-
669
- def append(self, value: MagnetSegment) -> None:
670
- return self._segments.append(value)
671
-
672
- def clear(self) -> None:
673
- self._segments.clear()
674
-
675
- def __contains__(self, value: Any, /) -> bool:
676
- return value in self._segments
677
-
678
- @property
679
- def cost(self) -> int:
680
- return sum(segment.cost for segment in self)
681
-
682
- def count(self, value: Any) -> int:
683
- return self._segments.count(value)
684
-
685
- def __delitem__(self, index: SupportsIndex | slice, /) -> None:
686
- del self._segments[index]
687
-
688
- def extend(self, iterable: Iterable[MagnetSegment], /) -> None:
689
- self._segments.extend(iterable)
690
-
691
- @overload
692
- def __getitem__(self, index: SupportsIndex, /) -> MagnetSegment:
693
- pass
694
-
695
- @overload
696
- def __getitem__(self, index: slice, /) -> MutableSequence[MagnetSegment]:
697
- pass
698
-
699
- def __getitem__(
700
- self, index: SupportsIndex | slice, /
701
- ) -> MagnetSegment | MutableSequence[MagnetSegment]:
702
- return self._segments[index]
703
-
704
- def __iadd__(self, value: Iterable[MagnetSegment], /) -> Self:
705
- self._segments += value
706
- return self
707
-
708
- def index(
709
- self, value: Any, start: SupportsIndex = 0, stop: SupportsIndex = maxsize, /
710
- ) -> int:
711
- return self._segments.index(value, start, stop)
712
-
713
- def insert(self, index: SupportsIndex, value: MagnetSegment, /) -> None:
714
- return self._segments.insert(index, value)
715
-
716
- def __iter__(self) -> Iterator[MagnetSegment]:
717
- return iter(self._segments)
718
-
719
- def __len__(self) -> int:
720
- return len(self._segments)
721
-
722
- def pop(self, index: SupportsIndex = -1, /) -> MagnetSegment:
723
- return self._segments.pop(index)
724
-
725
- def remove(self, value: MagnetSegment, /) -> None:
726
- self._segments.remove(value)
727
-
728
- def __repr__(self) -> str:
729
- return f"{type(self).__name__}({self._segments!r})"
730
-
731
- def reverse(self) -> None:
732
- self._segments.reverse()
733
-
734
- def __reversed__(self) -> Iterator[MagnetSegment]:
735
- return reversed(self._segments)
736
-
737
- @overload
738
- def __setitem__(self, index: SupportsIndex, value: MagnetSegment, /) -> None:
739
- pass
740
-
741
- @overload
742
- def __setitem__(self, slice: slice, value: Iterable[MagnetSegment], /) -> None:
743
- pass
744
-
745
- def __setitem__(
746
- self,
747
- index: SupportsIndex | slice,
748
- value: MagnetSegment | Iterable[MagnetSegment],
749
- /,
750
- ) -> None:
751
- self._segments[index] = value # type: ignore
752
-
753
- @property
754
- def x_pos(self) -> float:
755
- """Positive is right, negative is left"""
756
- return self[-1].x_pos
757
-
758
- @x_pos.setter
759
- def x_pos(self, value: float, /) -> None:
760
- offset: float = value - self.x_pos
761
- for segment in self:
762
- segment.x_pos += offset
763
-
764
- @property
765
- def x_rot(self) -> float:
766
- """Represented in degrees from 0 to 360
767
- Positive turns top face front, negative turns top face back
768
- """
769
- return self[-1].x_rot
770
-
771
- @x_rot.setter
772
- def x_rot(self, value: float, /) -> None:
773
- """Positions of segments are not updated."""
774
- offset: float = value - self.x_rot
775
- for segment in self:
776
- segment.x_rot += offset
777
-
778
- @property
779
- def y_pos(self) -> float:
780
- """Positive is up, negative is down"""
781
- return self[-1].y_pos
782
-
783
- @y_pos.setter
784
- def y_pos(self, value: float, /) -> None:
785
- offset: float = value - self.y_pos
786
- for segment in self:
787
- segment.y_pos += offset
788
-
789
- @property
790
- def y_rot(self) -> float:
791
- """Represented in degrees from 0 to 360
792
- Positive turns front face right, negative turns front face left
793
- """
794
- return self[-1].y_rot
795
-
796
- @y_rot.setter
797
- def y_rot(self, value: float, /) -> None:
798
- """Positions of segments are not updated."""
799
- offset: float = value - self.y_rot
800
- for segment in self:
801
- segment.y_rot += offset
802
-
803
- @property
804
- def z_pos(self) -> float:
805
- """Positive is front, negative is back"""
806
- return self[-1].z_pos
807
-
808
- @z_pos.setter
809
- def z_pos(self, value: float, /) -> None:
810
- offset: float = value - self.z_pos
811
- for segment in self:
812
- segment.z_pos += offset
813
-
814
- @property
815
- def z_rot(self) -> float:
816
- """Represented in degrees from 0 to 360
817
- Positive turns top face left, negative turns top face right
818
- """
819
- return self[-1].z_rot
820
-
821
- @z_rot.setter
822
- def z_rot(self, value: float, /) -> None:
823
- """Positions of segments are not updated."""
824
- offset: float = value - self.z_rot
825
- for segment in self:
826
- segment.z_rot += offset
827
-
828
-
829
- class DashTunnel(BasePart):
830
- __slots__ = ("_shape",)
831
-
832
- _shape: Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB]
833
-
834
- def __init__(
835
- self,
836
- x_pos: float,
837
- y_pos: float,
838
- z_pos: float,
839
- x_rot: float,
840
- y_rot: float,
841
- z_rot: float,
842
- *,
843
- shape: Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB],
844
- ) -> None:
845
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
846
- self.shape = shape
847
-
848
- @property
849
- def cost(self) -> Literal[35, 100]:
850
- match self.shape:
851
- case DeviceModel.DashTunnelA:
852
- return 35
853
- case DeviceModel.DashTunnelB:
854
- return 100
855
-
856
- def __repr__(self) -> str:
857
- return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
858
-
859
- @property
860
- def shape(self) -> Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB]:
861
- return self._shape
862
-
863
- @shape.setter
864
- def shape(
865
- self, value: Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB], /
866
- ) -> None:
867
- self._shape = value
868
-
869
-
870
- class SeesawBlock(BasePart):
871
- __slots__ = ("_auto", "_shape")
872
-
873
- _auto: bool
874
- _shape: Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock]
875
-
876
- def __init__(
877
- self,
878
- x_pos: float,
879
- y_pos: float,
880
- z_pos: float,
881
- x_rot: float,
882
- y_rot: float,
883
- z_rot: float,
884
- *,
885
- auto: bool = False,
886
- shape: Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock],
887
- ) -> None:
888
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
889
- self.auto = auto
890
- self.shape = shape
891
-
892
- @property
893
- def auto(self) -> bool:
894
- return self._auto
895
-
896
- @auto.setter
897
- def auto(self, value: bool, /) -> None:
898
- self._auto = value
899
-
900
- @property
901
- def cost(self) -> Literal[100]:
902
- return 100
903
-
904
- def __repr__(self) -> str:
905
- return (
906
- f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, auto={self.auto!r}, shape={self.shape!r})"
907
- if self.auto
908
- else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
909
- )
910
-
911
- @property
912
- def shape(self) -> Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock]:
913
- return self._shape
914
-
915
- @shape.setter
916
- def shape(
917
- self, value: Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock], /
918
- ) -> None:
919
- self._shape = value
920
-
921
-
922
- class Cannon(BasePart):
923
- __slots__ = ()
924
-
925
- @property
926
- def cost(self) -> Literal[30]:
927
- return 30
928
-
929
-
930
- class Drawbridge(BasePart):
931
- __slots__ = ()
932
-
933
- @property
934
- def cost(self) -> Literal[50]:
935
- return 50
936
-
937
-
938
- class Turntable(FixedSpeedDevice):
939
- __slots__ = ()
940
-
941
- @property
942
- def cost(self) -> Literal[50]:
943
- return 50
944
-
945
-
946
- class Bumper(BasePart):
947
- __slots__ = ("_powerful",)
948
-
949
- _powerful: bool
950
-
951
- def __init__(
952
- self,
953
- x_pos: float,
954
- y_pos: float,
955
- z_pos: float,
956
- x_rot: float,
957
- y_rot: float,
958
- z_rot: float,
959
- *,
960
- powerful: bool = False,
961
- ) -> None:
962
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
963
- self.powerful = powerful
964
-
965
- @property
966
- def cost(self) -> Literal[20]:
967
- return 20
968
-
969
- @property
970
- def powerful(self) -> bool:
971
- return self._powerful
972
-
973
- @powerful.setter
974
- def powerful(self, value: bool, /) -> None:
975
- self._powerful = value
976
-
977
- def __repr__(self) -> str:
978
- return (
979
- f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, powerful={self.powerful!r})"
980
- if self.powerful
981
- else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
982
- )
983
-
984
-
985
- class Thorn(BasePart):
986
- __slots__ = ()
987
-
988
- @property
989
- def cost(self) -> Literal[35]:
990
- return 35
991
-
992
-
993
- class Gear(FixedSpeedDevice):
994
- __slots__ = ()
995
-
996
- @property
997
- def cost(self) -> Literal[50]:
998
- return 50
999
-
1000
-
1001
- class Fan(BasePart):
1002
- __slots__ = ("_wind_pattern",)
1003
-
1004
- _wind_pattern: Literal[
1005
- DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan
1006
- ]
1007
-
1008
- def __init__(
1009
- self,
1010
- x_pos: float,
1011
- y_pos: float,
1012
- z_pos: float,
1013
- x_rot: float,
1014
- y_rot: float,
1015
- z_rot: float,
1016
- *,
1017
- wind_pattern: Literal[
1018
- DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan
1019
- ] = DeviceModel.Fan,
1020
- ) -> None:
1021
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1022
- self.wind_pattern = wind_pattern
1023
-
1024
- @property
1025
- def cost(self) -> Literal[50]:
1026
- return 50
1027
-
1028
- def __repr__(self) -> str:
1029
- return (
1030
- f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, wind_pattern={self.wind_pattern!r})"
1031
- if self.wind_pattern != DeviceModel.Fan
1032
- else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
1033
- )
1034
-
1035
- @property
1036
- def wind_pattern(
1037
- self,
1038
- ) -> Literal[DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan]:
1039
- return self._wind_pattern
1040
-
1041
- @wind_pattern.setter
1042
- def wind_pattern(
1043
- self,
1044
- value: Literal[DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan],
1045
- /,
1046
- ) -> None:
1047
- self._wind_pattern = value
1048
-
1049
-
1050
- class Spring(BasePart):
1051
- __slots__ = ()
1052
-
1053
- @property
1054
- def cost(self) -> Literal[25]:
1055
- return 25
1056
-
1057
-
1058
- @unique
1059
- class MovementTiming(Enum):
1060
- A = 23
1061
- B = 39
1062
- C = 55
1063
-
1064
-
1065
- class TimedDevice(BasePart, ABC):
1066
- """Device Template for devices which can have one of three timings for their movements"""
1067
-
1068
- __slots__ = ("_timing",)
1069
-
1070
- _timing: MovementTiming
1071
-
1072
- def __init__(
1073
- self,
1074
- x_pos: float,
1075
- y_pos: float,
1076
- z_pos: float,
1077
- x_rot: float,
1078
- y_rot: float,
1079
- z_rot: float,
1080
- *,
1081
- timing: MovementTiming,
1082
- ) -> None:
1083
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1084
- self.timing = timing
1085
-
1086
- def __repr__(self) -> str:
1087
- return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, timing={self.timing!r})"
1088
-
1089
- @property
1090
- def timing(self) -> MovementTiming:
1091
- return self._timing
1092
-
1093
- @timing.setter
1094
- def timing(self, value: MovementTiming, /) -> None:
1095
- self._timing = value
1096
-
1097
-
1098
- class Punch(TimedDevice):
1099
- __slots__ = ()
1100
-
1101
- @property
1102
- def cost(self) -> Literal[30]:
1103
- return 30
1104
-
1105
-
1106
- class Press(TimedDevice):
1107
- __slots__ = ()
1108
-
1109
- @property
1110
- def cost(self) -> Literal[50]:
1111
- return 50
1112
-
1113
-
1114
- class Scissors(TimedDevice):
1115
- __slots__ = ()
1116
-
1117
- @property
1118
- def cost(self) -> Literal[70]:
1119
- return 70
1120
-
1121
-
1122
- class MagnifyingGlass(BasePart):
1123
- __slots__ = ()
1124
-
1125
- @property
1126
- def cost(self) -> Literal[200]:
1127
- return 200
1128
-
1129
-
1130
- class UpsideDownStageDevice(BasePart):
1131
- __slots__ = ()
1132
-
1133
- @property
1134
- def cost(self) -> Literal[50]:
1135
- return 50
1136
-
1137
-
1138
- class UpsideDownBall(BasePart):
1139
- __slots__ = ()
1140
-
1141
- @property
1142
- def cost(self) -> Literal[25]:
1143
- return 25
1144
-
1145
-
1146
- class SizeTunnel(BasePart):
1147
- __slots__ = ("_size",)
1148
-
1149
- _size: Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel]
1150
-
1151
- def __init__(
1152
- self,
1153
- x_pos: float,
1154
- y_pos: float,
1155
- z_pos: float,
1156
- x_rot: float,
1157
- y_rot: float,
1158
- z_rot: float,
1159
- *,
1160
- size: Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel],
1161
- ) -> None:
1162
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1163
- self.size = size
1164
-
1165
- @property
1166
- def cost(self) -> Literal[50]:
1167
- return 50
1168
-
1169
- def __repr__(self) -> str:
1170
- return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, size={self.size!r})"
1171
-
1172
- @property
1173
- def size(self) -> Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel]:
1174
- return self._size
1175
-
1176
- @size.setter
1177
- def size(
1178
- self, value: Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel], /
1179
- ) -> None:
1180
- self._size = value
1181
-
1182
-
1183
- class TrainTrack(BasePart):
1184
- __slots__ = ("_shape",)
1185
-
1186
- _shape: Literal[
1187
- DeviceModel.EndTracks,
1188
- DeviceModel.LeftTracks,
1189
- DeviceModel.RightTracks,
1190
- DeviceModel.StraightTracks,
1191
- ]
1192
-
1193
- def __init__(
1194
- self,
1195
- x_pos: float,
1196
- y_pos: float,
1197
- z_pos: float,
1198
- x_rot: float,
1199
- y_rot: float,
1200
- z_rot: float,
1201
- *,
1202
- shape: Literal[
1203
- DeviceModel.EndTracks,
1204
- DeviceModel.LeftTracks,
1205
- DeviceModel.RightTracks,
1206
- DeviceModel.StraightTracks,
1207
- ],
1208
- ) -> None:
1209
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1210
- self.shape = shape
1211
-
1212
- @property
1213
- def cost(self) -> Literal[0, 20]:
1214
- return 0 if self.shape is DeviceModel.EndTracks else 20
1215
-
1216
- def __repr__(self) -> str:
1217
- return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
1218
-
1219
- @property
1220
- def shape(
1221
- self,
1222
- ) -> Literal[
1223
- DeviceModel.EndTracks,
1224
- DeviceModel.LeftTracks,
1225
- DeviceModel.RightTracks,
1226
- DeviceModel.StraightTracks,
1227
- ]:
1228
- return self._shape
1229
-
1230
- @shape.setter
1231
- def shape(
1232
- self,
1233
- value: Literal[
1234
- DeviceModel.EndTracks,
1235
- DeviceModel.LeftTracks,
1236
- DeviceModel.RightTracks,
1237
- DeviceModel.StraightTracks,
1238
- ],
1239
- /,
1240
- ) -> None:
1241
- self._shape = value
1242
-
1243
-
1244
- class ToyTrain(BasePart, MutableSequence[TrainTrack]):
1245
- __slots__ = ("_tracks",)
1246
-
1247
- _tracks: list[TrainTrack]
1248
-
1249
- def __init__(
1250
- self,
1251
- x_pos: float,
1252
- y_pos: float,
1253
- z_pos: float,
1254
- x_rot: float,
1255
- y_rot: float,
1256
- z_rot: float,
1257
- *,
1258
- tracks: Iterable[TrainTrack] = (),
1259
- ) -> None:
1260
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1261
- self._tracks = list(tracks)
1262
-
1263
- def append(self, value: TrainTrack) -> None:
1264
- return self._tracks.append(value)
1265
-
1266
- def clear(self) -> None:
1267
- self._tracks.clear()
1268
-
1269
- def __contains__(self, value: Any, /) -> bool:
1270
- return value in self._tracks
1271
-
1272
- @property
1273
- def cost(self) -> int:
1274
- return 100 + sum(track.cost for track in self)
1275
-
1276
- def count(self, value: Any) -> int:
1277
- return self._tracks.count(value)
1278
-
1279
- def __delitem__(self, index: SupportsIndex | slice, /) -> None:
1280
- del self._tracks[index]
1281
-
1282
- def extend(self, iterable: Iterable[TrainTrack], /) -> None:
1283
- self._tracks.extend(iterable)
1284
-
1285
- @overload
1286
- def __getitem__(self, index: SupportsIndex, /) -> TrainTrack:
1287
- pass
1288
-
1289
- @overload
1290
- def __getitem__(self, index: slice, /) -> MutableSequence[TrainTrack]:
1291
- pass
1292
-
1293
- def __getitem__(
1294
- self, index: SupportsIndex | slice, /
1295
- ) -> TrainTrack | MutableSequence[TrainTrack]:
1296
- return self._tracks[index]
1297
-
1298
- def __iadd__(self, value: Iterable[TrainTrack], /) -> Self:
1299
- self._tracks += value
1300
- return self
1301
-
1302
- def index(
1303
- self, value: Any, start: SupportsIndex = 0, stop: SupportsIndex = maxsize, /
1304
- ) -> int:
1305
- return self._tracks.index(value, start, stop)
1306
-
1307
- def insert(self, index: SupportsIndex, value: TrainTrack, /) -> None:
1308
- return self._tracks.insert(index, value)
1309
-
1310
- def __iter__(self) -> Iterator[TrainTrack]:
1311
- return iter(self._tracks)
1312
-
1313
- def __len__(self) -> int:
1314
- return len(self._tracks)
1315
-
1316
- def pop(self, index: SupportsIndex = -1, /) -> TrainTrack:
1317
- return self._tracks.pop(index)
1318
-
1319
- def remove(self, value: TrainTrack, /) -> None:
1320
- self._tracks.remove(value)
1321
-
1322
- def __repr__(self) -> str:
1323
- return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, tracks={self._tracks!r})"
1324
-
1325
- def reverse(self) -> None:
1326
- self._tracks.reverse()
1327
-
1328
- def __reversed__(self) -> Iterator[TrainTrack]:
1329
- return reversed(self._tracks)
1330
-
1331
- @overload
1332
- def __setitem__(self, index: SupportsIndex, value: TrainTrack, /) -> None:
1333
- pass
1334
-
1335
- @overload
1336
- def __setitem__(self, slice: slice, value: Iterable[TrainTrack], /) -> None:
1337
- pass
1338
-
1339
- def __setitem__(
1340
- self, index: SupportsIndex | slice, value: TrainTrack | Iterable[TrainTrack], /
1341
- ) -> None:
1342
- self._tracks[index] = value # type: ignore
1343
-
1344
-
1345
- class Warp(BasePart):
1346
- __slots__ = (
1347
- "_dest_x",
1348
- "_dest_y",
1349
- "_dest_z",
1350
- "_return_dest_x",
1351
- "_return_dest_y",
1352
- "_return_dest_z",
1353
- "_return_x_pos",
1354
- "_return_x_rot",
1355
- "_return_y_pos",
1356
- "_return_y_rot",
1357
- "_return_z_pos",
1358
- "_return_z_rot",
1359
- )
1360
-
1361
- _dest_x: float
1362
- _dest_y: float
1363
- _dest_z: float
1364
- _return_dest_x: float
1365
- _return_dest_y: float
1366
- _return_dest_z: float
1367
- _return_x_pos: float
1368
- _return_x_rot: float
1369
- _return_y_pos: float
1370
- _return_y_rot: float
1371
- _return_z_pos: float
1372
- _return_z_rot: float
1373
-
1374
- def __init__(
1375
- self,
1376
- x_pos: float,
1377
- y_pos: float,
1378
- z_pos: float,
1379
- x_rot: float,
1380
- y_rot: float,
1381
- z_rot: float,
1382
- *,
1383
- dest_x: float,
1384
- dest_y: float,
1385
- dest_z: float,
1386
- return_x_pos: float,
1387
- return_y_pos: float,
1388
- return_z_pos: float,
1389
- return_x_rot: float,
1390
- return_y_rot: float,
1391
- return_z_rot: float,
1392
- return_dest_x: float,
1393
- return_dest_y: float,
1394
- return_dest_z: float,
1395
- ) -> None:
1396
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1397
- self.dest_x = dest_x
1398
- self.dest_y = dest_y
1399
- self.dest_z = dest_z
1400
- self.return_dest_x = return_dest_x
1401
- self.return_dest_y = return_dest_y
1402
- self.return_dest_z = return_dest_z
1403
- self.return_x_pos = return_x_pos
1404
- self.return_x_rot = return_x_rot
1405
- self.return_y_pos = return_y_pos
1406
- self.return_y_rot = return_y_rot
1407
- self.return_z_pos = return_z_pos
1408
- self.return_z_rot = return_z_rot
1409
-
1410
- @property
1411
- def cost(self) -> Literal[25]:
1412
- return 25
1413
-
1414
- @property
1415
- def dest_x(self) -> float:
1416
- """Positive is right, negative is left"""
1417
- return self._dest_x
1418
-
1419
- @dest_x.setter
1420
- def dest_x(self, value: float, /) -> None:
1421
- self._dest_x = value
1422
-
1423
- @property
1424
- def dest_y(self) -> float:
1425
- return self._dest_y
1426
-
1427
- @dest_y.setter
1428
- def dest_y(self, value: float, /) -> None:
1429
- """Positive is up, negative is down"""
1430
- self._dest_y = value
1431
-
1432
- @property
1433
- def dest_z(self) -> float:
1434
- """Positive is front, negative is back"""
1435
- return self._dest_z
1436
-
1437
- @dest_z.setter
1438
- def dest_z(self, value: float, /) -> None:
1439
- self._dest_z = value
1440
-
1441
- def __repr__(self) -> str:
1442
- return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, dest_x={self.dest_x!r}, dest_y={self.dest_y!r}, dest_z={self.dest_z!r}, return_x_pos={self.return_x_pos}, return_y_pos={self.return_y_pos}, return_z_pos={self.return_z_pos}, return_x_rot={self.return_x_rot}, return_y_rot={self.return_y_rot}, return_z_rot={self.return_z_rot}, return_dest_x={self.return_dest_x}, return_dest_y={self.return_dest_y}, return_dest_z={self.return_dest_z})"
1443
-
1444
- @property
1445
- def return_dest_x(self) -> float:
1446
- """Positive is right, negative is left"""
1447
- return self._return_dest_x
1448
-
1449
- @return_dest_x.setter
1450
- def return_dest_x(self, value: float, /) -> None:
1451
- self._return_dest_x = value
1452
-
1453
- @property
1454
- def return_dest_y(self) -> float:
1455
- return self._return_dest_y
1456
-
1457
- @return_dest_y.setter
1458
- def return_dest_y(self, value: float, /) -> None:
1459
- """Positive is up, negative is down"""
1460
- self._return_dest_y = value
1461
-
1462
- @property
1463
- def return_dest_z(self) -> float:
1464
- """Positive is front, negative is back"""
1465
- return self._return_dest_z
1466
-
1467
- @return_dest_z.setter
1468
- def return_dest_z(self, value: float, /) -> None:
1469
- self._return_dest_z = value
1470
-
1471
- @property
1472
- def return_x_pos(self) -> float:
1473
- """Positive is right, negative is left"""
1474
- return self._return_x_pos
1475
-
1476
- @return_x_pos.setter
1477
- def return_x_pos(self, value: float, /) -> None:
1478
- self._return_x_pos = value
1479
-
1480
- @property
1481
- def return_x_rot(self) -> float:
1482
- """Represented in degrees from 0 to 360
1483
- Positive turns top face front, negative turns top face back
1484
- """
1485
- return self._return_x_rot
1486
-
1487
- @return_x_rot.setter
1488
- def return_x_rot(self, value: float, /) -> None:
1489
- self._return_x_rot = value % 360
1490
-
1491
- @property
1492
- def return_y_pos(self) -> float:
1493
- """Positive is up, negative is down"""
1494
- return self._return_y_pos
1495
-
1496
- @return_y_pos.setter
1497
- def return_y_pos(self, value: float, /) -> None:
1498
- self._return_y_pos = value
1499
-
1500
- @property
1501
- def return_y_rot(self) -> float:
1502
- """Represented in degrees from 0 to 360
1503
- Positive turns front face right, negative turns front face left
1504
- """
1505
- return self._return_y_rot
1506
-
1507
- @return_y_rot.setter
1508
- def return_y_rot(self, value: float, /) -> None:
1509
- self._return_y_rot = value % 360
1510
-
1511
- @property
1512
- def return_z_pos(self) -> float:
1513
- """Positive is front, negative is back"""
1514
- return self._return_z_pos
1515
-
1516
- @return_z_pos.setter
1517
- def return_z_pos(self, value: float, /) -> None:
1518
- self._return_z_pos = value
1519
-
1520
- @property
1521
- def return_z_rot(self) -> float:
1522
- """Represented in degrees from 0 to 360
1523
- Positive turns top face left, negative turns top face right
1524
- """
1525
- return self._return_z_rot
1526
-
1527
- @return_z_rot.setter
1528
- def return_z_rot(self, value: float, /) -> None:
1529
- self._return_z_rot = value % 360
1530
-
1531
-
1532
- class BlinkingTile(TimedDevice):
1533
- __slots__ = ()
1534
-
1535
- @property
1536
- def cost(self) -> Literal[20]:
1537
- return 20
1538
-
1539
-
1540
- class MelodyTile(BasePart):
1541
- __slots__ = ("_note",)
1542
-
1543
- _note: Literal[
1544
- DeviceModel.MelodyTileLowG,
1545
- DeviceModel.MelodyTileLowGSharp,
1546
- DeviceModel.MelodyTileLowA,
1547
- DeviceModel.MelodyTileLowASharp,
1548
- DeviceModel.MelodyTileLowB,
1549
- DeviceModel.MelodyTileC,
1550
- DeviceModel.MelodyTileCSharp,
1551
- DeviceModel.MelodyTileD,
1552
- DeviceModel.MelodyTileDSharp,
1553
- DeviceModel.MelodyTileE,
1554
- DeviceModel.MelodyTileF,
1555
- DeviceModel.MelodyTileFSharp,
1556
- DeviceModel.MelodyTileG,
1557
- DeviceModel.MelodyTileGSharp,
1558
- DeviceModel.MelodyTileA,
1559
- DeviceModel.MelodyTileASharp,
1560
- DeviceModel.MelodyTileB,
1561
- DeviceModel.MelodyTileHighC,
1562
- DeviceModel.MelodyTileHighCSharp,
1563
- DeviceModel.MelodyTileHighD,
1564
- DeviceModel.MelodyTileHighDSharp,
1565
- DeviceModel.MelodyTileHighE,
1566
- DeviceModel.MelodyTileHighF,
1567
- DeviceModel.MelodyTileHighFSharp,
1568
- DeviceModel.MelodyTileHighG,
1569
- ]
1570
-
1571
- def __init__(
1572
- self,
1573
- x_pos: float,
1574
- y_pos: float,
1575
- z_pos: float,
1576
- x_rot: float,
1577
- y_rot: float,
1578
- z_rot: float,
1579
- *,
1580
- note: Literal[
1581
- DeviceModel.MelodyTileLowG,
1582
- DeviceModel.MelodyTileLowGSharp,
1583
- DeviceModel.MelodyTileLowA,
1584
- DeviceModel.MelodyTileLowASharp,
1585
- DeviceModel.MelodyTileLowB,
1586
- DeviceModel.MelodyTileC,
1587
- DeviceModel.MelodyTileCSharp,
1588
- DeviceModel.MelodyTileD,
1589
- DeviceModel.MelodyTileDSharp,
1590
- DeviceModel.MelodyTileE,
1591
- DeviceModel.MelodyTileF,
1592
- DeviceModel.MelodyTileFSharp,
1593
- DeviceModel.MelodyTileG,
1594
- DeviceModel.MelodyTileGSharp,
1595
- DeviceModel.MelodyTileA,
1596
- DeviceModel.MelodyTileASharp,
1597
- DeviceModel.MelodyTileB,
1598
- DeviceModel.MelodyTileHighC,
1599
- DeviceModel.MelodyTileHighCSharp,
1600
- DeviceModel.MelodyTileHighD,
1601
- DeviceModel.MelodyTileHighDSharp,
1602
- DeviceModel.MelodyTileHighE,
1603
- DeviceModel.MelodyTileHighF,
1604
- DeviceModel.MelodyTileHighFSharp,
1605
- DeviceModel.MelodyTileHighG,
1606
- ],
1607
- ) -> None:
1608
- super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1609
- self.note = note
1610
-
1611
- @property
1612
- def cost(self) -> Literal[20]:
1613
- return 20
1614
-
1615
- @property
1616
- def note(self) -> Literal[
1617
- DeviceModel.MelodyTileLowG,
1618
- DeviceModel.MelodyTileLowGSharp,
1619
- DeviceModel.MelodyTileLowA,
1620
- DeviceModel.MelodyTileLowASharp,
1621
- DeviceModel.MelodyTileLowB,
1622
- DeviceModel.MelodyTileC,
1623
- DeviceModel.MelodyTileCSharp,
1624
- DeviceModel.MelodyTileD,
1625
- DeviceModel.MelodyTileDSharp,
1626
- DeviceModel.MelodyTileE,
1627
- DeviceModel.MelodyTileF,
1628
- DeviceModel.MelodyTileFSharp,
1629
- DeviceModel.MelodyTileG,
1630
- DeviceModel.MelodyTileGSharp,
1631
- DeviceModel.MelodyTileA,
1632
- DeviceModel.MelodyTileASharp,
1633
- DeviceModel.MelodyTileB,
1634
- DeviceModel.MelodyTileHighC,
1635
- DeviceModel.MelodyTileHighCSharp,
1636
- DeviceModel.MelodyTileHighD,
1637
- DeviceModel.MelodyTileHighDSharp,
1638
- DeviceModel.MelodyTileHighE,
1639
- DeviceModel.MelodyTileHighF,
1640
- DeviceModel.MelodyTileHighFSharp,
1641
- DeviceModel.MelodyTileHighG,
1642
- ]:
1643
- return self._note
1644
-
1645
- @note.setter
1646
- def note(
1647
- self,
1648
- value: Literal[
1649
- DeviceModel.MelodyTileLowG,
1650
- DeviceModel.MelodyTileLowGSharp,
1651
- DeviceModel.MelodyTileLowA,
1652
- DeviceModel.MelodyTileLowASharp,
1653
- DeviceModel.MelodyTileLowB,
1654
- DeviceModel.MelodyTileC,
1655
- DeviceModel.MelodyTileCSharp,
1656
- DeviceModel.MelodyTileD,
1657
- DeviceModel.MelodyTileDSharp,
1658
- DeviceModel.MelodyTileE,
1659
- DeviceModel.MelodyTileF,
1660
- DeviceModel.MelodyTileFSharp,
1661
- DeviceModel.MelodyTileG,
1662
- DeviceModel.MelodyTileGSharp,
1663
- DeviceModel.MelodyTileA,
1664
- DeviceModel.MelodyTileASharp,
1665
- DeviceModel.MelodyTileB,
1666
- DeviceModel.MelodyTileHighC,
1667
- DeviceModel.MelodyTileHighCSharp,
1668
- DeviceModel.MelodyTileHighD,
1669
- DeviceModel.MelodyTileHighDSharp,
1670
- DeviceModel.MelodyTileHighE,
1671
- DeviceModel.MelodyTileHighF,
1672
- DeviceModel.MelodyTileHighFSharp,
1673
- DeviceModel.MelodyTileHighG,
1674
- ],
1675
- /,
1676
- ) -> None:
1677
- self._note = value
1678
-
1679
- def __repr__(self) -> str:
1680
- return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, note={self.note!r})"
1681
-
1682
-
1683
- class KororinCapsule(BasePart):
1684
- __slots__ = ()
1685
-
1686
- @property
1687
- def cost(self) -> Literal[0]:
1688
- return 0
1689
-
1690
-
1691
- class GreenCrystal(BasePart):
1692
- __slots__ = ()
1693
-
1694
- @property
1695
- def cost(self) -> Literal[0]:
1696
- return 0
1697
-
1698
-
1699
- class Ant(BasePart):
1700
- __slots__ = ()
1701
-
1702
- @property
1703
- def cost(self) -> Literal[0]:
1704
- return 0
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from collections.abc import Iterable, MutableSequence
5
+ from enum import Enum, unique, Flag
6
+ from operator import index
7
+ from sys import maxsize
8
+ from typing import Any, Final, Iterator, Literal, Self, SupportsIndex, overload
9
+
10
+ from .model import DecorationModel, DeviceModel, PartModel
11
+
12
+
13
+ __all__ = [
14
+ "Ant",
15
+ "BasePart",
16
+ "BlinkingTile",
17
+ "Bumper",
18
+ "Cannon",
19
+ "ConveyorBelt",
20
+ "DashTunnel",
21
+ "Drawbridge",
22
+ "Fan",
23
+ "FixedSpeedDevice",
24
+ "Gear",
25
+ "Goal",
26
+ "GreenCrystal",
27
+ "KororinCapsule",
28
+ "Magnet",
29
+ "MagnetSegment",
30
+ "MagnifyingGlass",
31
+ "MelodyTile",
32
+ "MovingCurve",
33
+ "MovingTile",
34
+ "Part",
35
+ "Press",
36
+ "ProgressMarker",
37
+ "Punch",
38
+ "Scissors",
39
+ "SeesawBlock",
40
+ "SizeTunnel",
41
+ "SlidingTile",
42
+ "Spring",
43
+ "Start",
44
+ "Thorn",
45
+ "TimedDevice",
46
+ "ToyTrain",
47
+ "TrainTrack",
48
+ "Turntable",
49
+ "UpsideDownBall",
50
+ "UpsideDownStageDevice",
51
+ "Walls",
52
+ "Warp",
53
+ ]
54
+
55
+
56
+ class BasePart(ABC):
57
+ """Base class for all stage elements"""
58
+
59
+ __match_args__ = ("x_pos", "y_pos", "z_pos", "x_rot", "y_rot", "z_rot")
60
+ __slots__ = ("_x_pos", "_x_rot", "_y_pos", "_y_rot", "_z_pos", "_z_rot")
61
+
62
+ _x_pos: float
63
+ _x_rot: float
64
+ _y_pos: float
65
+ _y_rot: float
66
+ _z_pos: float
67
+ _z_rot: float
68
+
69
+ def __init__(
70
+ self,
71
+ x_pos: float,
72
+ y_pos: float,
73
+ z_pos: float,
74
+ x_rot: float,
75
+ y_rot: float,
76
+ z_rot: float,
77
+ ) -> None:
78
+ self.x_pos = x_pos
79
+ self.x_rot = x_rot
80
+ self.y_pos = y_pos
81
+ self.y_rot = y_rot
82
+ self.z_pos = z_pos
83
+ self.z_rot = z_rot
84
+
85
+ @property
86
+ @abstractmethod
87
+ def cost(self) -> int:
88
+ """The number of kororin points that this part costs to place."""
89
+
90
+ def __repr__(self) -> str:
91
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
92
+
93
+ @property
94
+ def x_pos(self) -> float:
95
+ """Positive is right, negative is left"""
96
+ return self._x_pos
97
+
98
+ @x_pos.setter
99
+ def x_pos(self, value: float, /) -> None:
100
+ self._x_pos = value
101
+
102
+ @property
103
+ def x_rot(self) -> float:
104
+ """Represented in degrees from 0 to 360
105
+ Positive turns top face front, negative turns top face back
106
+ """
107
+ return self._x_rot
108
+
109
+ @x_rot.setter
110
+ def x_rot(self, value: float, /) -> None:
111
+ self._x_rot = value % 360
112
+
113
+ @property
114
+ def y_pos(self) -> float:
115
+ """Positive is up, negative is down"""
116
+ return self._y_pos
117
+
118
+ @y_pos.setter
119
+ def y_pos(self, value: float, /) -> None:
120
+ self._y_pos = value
121
+
122
+ @property
123
+ def y_rot(self) -> float:
124
+ """Represented in degrees from 0 to 360
125
+ Positive turns front face right, negative turns front face left
126
+ """
127
+ return self._y_rot
128
+
129
+ @y_rot.setter
130
+ def y_rot(self, value: float, /) -> None:
131
+ self._y_rot = value % 360
132
+
133
+ @property
134
+ def z_pos(self) -> float:
135
+ """Positive is front, negative is back"""
136
+ return self._z_pos
137
+
138
+ @z_pos.setter
139
+ def z_pos(self, value: float, /) -> None:
140
+ self._z_pos = value
141
+
142
+ @property
143
+ def z_rot(self) -> float:
144
+ """Represented in degrees from 0 to 360
145
+ Positive turns top face left, negative turns top face right
146
+ """
147
+ return self._z_rot
148
+
149
+ @z_rot.setter
150
+ def z_rot(self, value: float, /) -> None:
151
+ self._z_rot = value % 360
152
+
153
+
154
+ class Part(BasePart):
155
+ """(Usually) static model that has no behavior other than being solid."""
156
+
157
+ __slots__ = ("_shape",)
158
+
159
+ _shape: PartModel | DecorationModel
160
+
161
+ def __init__(
162
+ self,
163
+ x_pos: float,
164
+ y_pos: float,
165
+ z_pos: float,
166
+ x_rot: float,
167
+ y_rot: float,
168
+ z_rot: float,
169
+ *,
170
+ shape: PartModel | DecorationModel,
171
+ ) -> None:
172
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
173
+ self.shape = shape
174
+
175
+ @property
176
+ def cost(self) -> Literal[10, 15, 20]:
177
+ if isinstance(self.shape, DecorationModel):
178
+ return 20
179
+ elif self.shape in frozenset(
180
+ {
181
+ PartModel.MagmaTile,
182
+ PartModel.SlipperyTile,
183
+ PartModel.StickyTile,
184
+ PartModel.InvisibleTile,
185
+ }
186
+ ):
187
+ return 15
188
+ else:
189
+ return 10
190
+
191
+ @property
192
+ def shape(self) -> PartModel | DecorationModel:
193
+ return self._shape
194
+
195
+ @shape.setter
196
+ def shape(self, value: PartModel | DecorationModel, /) -> None:
197
+ self._shape = value
198
+
199
+ def __repr__(self) -> str:
200
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
201
+
202
+
203
+ class Start(BasePart):
204
+ __slots__ = ()
205
+
206
+ @property
207
+ def cost(self) -> Literal[0]:
208
+ return 0
209
+
210
+
211
+ class Goal(BasePart):
212
+ __slots__ = ()
213
+
214
+ @property
215
+ def cost(self) -> Literal[0]:
216
+ return 0
217
+
218
+
219
+ class ProgressMarker(BasePart):
220
+ """Either a crystal (when progress is odd) or a respawn (when progress is even)"""
221
+
222
+ __slots__ = ("_progress",)
223
+
224
+ _progress: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
225
+
226
+ def __init__(
227
+ self,
228
+ x_pos: float,
229
+ y_pos: float,
230
+ z_pos: float,
231
+ x_rot: float,
232
+ y_rot: float,
233
+ z_rot: float,
234
+ *,
235
+ progress: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
236
+ ) -> None:
237
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
238
+ self.progress = progress
239
+
240
+ @property
241
+ def cost(self) -> Literal[15, 0]:
242
+ return 15 if self.progress & 1 else 0
243
+
244
+ @property
245
+ def progress(self) -> Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
246
+ """The number displayed above the object. Controls which crystals are required to enable a respawn."""
247
+ return self._progress
248
+
249
+ @progress.setter
250
+ def progress(self, value: Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], /):
251
+ self._progress = value
252
+
253
+ def __repr__(self) -> str:
254
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, progress={self.progress!r})"
255
+
256
+
257
+ @unique
258
+ class Speed(Enum):
259
+ SLOW = 55
260
+ NORMAL = 39
261
+ FAST = 23
262
+
263
+
264
+ @unique
265
+ class Walls(Flag):
266
+ BACK = 8
267
+ FRONT = 2
268
+ LEFT = 4
269
+ RIGHT = 1
270
+
271
+
272
+ class MovingTile(BasePart):
273
+ """A part that moves in a straight line."""
274
+
275
+ __slots__ = (
276
+ "_dest_x",
277
+ "_dest_y",
278
+ "_dest_z",
279
+ "_shape",
280
+ "_speed",
281
+ "_switch",
282
+ "_walls",
283
+ )
284
+
285
+ _dest_x: float
286
+ _dest_y: float
287
+ _dest_z: float
288
+ _shape: Literal[
289
+ PartModel.Tile10x10,
290
+ PartModel.Tile20x20,
291
+ PartModel.TileA30x30,
292
+ PartModel.TileA30x90,
293
+ PartModel.Tile90x90,
294
+ PartModel.HoleB90x90,
295
+ PartModel.FunnelPipe,
296
+ PartModel.StraightPipe,
297
+ ]
298
+ _speed: float
299
+ _switch: bool
300
+ _walls: Walls
301
+
302
+ def __init__(
303
+ self,
304
+ x_pos: float,
305
+ y_pos: float,
306
+ z_pos: float,
307
+ x_rot: float,
308
+ y_rot: float,
309
+ z_rot: float,
310
+ *,
311
+ dest_x: float,
312
+ dest_y: float,
313
+ dest_z: float,
314
+ shape: Literal[
315
+ PartModel.Tile10x10,
316
+ PartModel.Tile20x20,
317
+ PartModel.TileA30x30,
318
+ PartModel.TileA30x90,
319
+ PartModel.Tile90x90,
320
+ PartModel.HoleB90x90,
321
+ PartModel.FunnelPipe,
322
+ PartModel.StraightPipe,
323
+ ],
324
+ speed: Speed | float = Speed.NORMAL,
325
+ switch: bool = False,
326
+ walls: Walls = Walls(0),
327
+ ) -> None:
328
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
329
+ self.dest_x = dest_x
330
+ self.dest_y = dest_y
331
+ self.dest_z = dest_z
332
+ self._shape = shape
333
+ self.speed = speed # type: ignore[assignment]
334
+ self.switch = switch
335
+ self.walls = walls
336
+
337
+ @property
338
+ def cost(self) -> Literal[20, 70, 35, 40, 50, 65]:
339
+ match self.shape:
340
+ case PartModel.Tile10x10:
341
+ return 20
342
+ case PartModel.Tile20x20 | PartModel.TileA30x30:
343
+ return 70
344
+ case PartModel.TileA30x90:
345
+ return 35
346
+ case PartModel.Tile90x90 | PartModel.HoleB90x90:
347
+ return 40
348
+ case PartModel.FunnelPipe:
349
+ return 50
350
+ case PartModel.StraightPipe:
351
+ return 65
352
+
353
+ @property
354
+ def dest_x(self) -> float:
355
+ """Positive is right, negative is left"""
356
+ return self._dest_x
357
+
358
+ @dest_x.setter
359
+ def dest_x(self, value: float, /) -> None:
360
+ self._dest_x = value
361
+
362
+ @property
363
+ def dest_y(self) -> float:
364
+ return self._dest_y
365
+
366
+ @dest_y.setter
367
+ def dest_y(self, value: float, /) -> None:
368
+ """Positive is up, negative is down"""
369
+ self._dest_y = value
370
+
371
+ @property
372
+ def dest_z(self) -> float:
373
+ """Positive is front, negative is back"""
374
+ return self._dest_z
375
+
376
+ @dest_z.setter
377
+ def dest_z(self, value: float, /) -> None:
378
+ self._dest_z = value
379
+
380
+ def __repr__(self) -> str:
381
+ output: Final[list[str]] = [
382
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, dest_x={self.dest_x!r}, dest_y={self.dest_y!r}, dest_z={self.dest_z!r}, shape={self.shape!r}"
383
+ ]
384
+ if self.speed != 1.0:
385
+ output.append(f", speed={self.speed!r}")
386
+ if self.switch:
387
+ output.append(f", switch={self.switch!r}")
388
+ if self.walls:
389
+ output.append(f", walls={self.walls!r}")
390
+ output.append(")")
391
+ return "".join(output)
392
+
393
+ @property
394
+ def shape(
395
+ self,
396
+ ) -> Literal[
397
+ PartModel.Tile10x10,
398
+ PartModel.Tile20x20,
399
+ PartModel.TileA30x30,
400
+ PartModel.TileA30x90,
401
+ PartModel.Tile90x90,
402
+ PartModel.HoleB90x90,
403
+ PartModel.FunnelPipe,
404
+ PartModel.StraightPipe,
405
+ ]:
406
+ """The appearance of this moving tile."""
407
+ return self._shape
408
+
409
+ @shape.setter
410
+ def shape(
411
+ self,
412
+ value: Literal[
413
+ PartModel.Tile10x10,
414
+ PartModel.Tile20x20,
415
+ PartModel.TileA30x30,
416
+ PartModel.TileA30x90,
417
+ PartModel.Tile90x90,
418
+ PartModel.HoleB90x90,
419
+ PartModel.FunnelPipe,
420
+ PartModel.StraightPipe,
421
+ ],
422
+ /,
423
+ ) -> None:
424
+ if (
425
+ value in frozenset({PartModel.FunnelPipe, PartModel.StraightPipe})
426
+ and self.switch
427
+ ):
428
+ raise ValueError("Moving pipes cannot be switches")
429
+ elif (
430
+ value not in frozenset({PartModel.Tile20x20, PartModel.TileA30x30})
431
+ and self.walls
432
+ ):
433
+ raise ValueError("Invalid shape for wall attachment")
434
+ else:
435
+ self._shape = value
436
+
437
+ @property
438
+ def speed(self) -> float:
439
+ return self._speed
440
+
441
+ @speed.setter
442
+ def speed(self, value: Speed | float, /) -> None:
443
+ match value:
444
+ case Speed.SLOW:
445
+ self.speed = 0.5
446
+ case Speed.NORMAL:
447
+ self.speed = 1.0
448
+ case Speed.FAST:
449
+ self.speed = 1.5
450
+ case _:
451
+ self._speed = value
452
+
453
+ @property
454
+ def switch(self) -> bool:
455
+ """Whether this moving tile must be touched in order to move."""
456
+ return self._switch
457
+
458
+ @switch.setter
459
+ def switch(self, value: bool, /) -> None:
460
+ if value and self.shape in frozenset(
461
+ {PartModel.FunnelPipe, PartModel.StraightPipe}
462
+ ):
463
+ raise ValueError("Moving pipes cannot be switches")
464
+ else:
465
+ self._switch = value
466
+
467
+ @property
468
+ def walls(self) -> Walls:
469
+ return self._walls
470
+
471
+ @walls.setter
472
+ def walls(self, value: Walls, /) -> None:
473
+ if not value or self.shape in frozenset(
474
+ {PartModel.Tile20x20, PartModel.TileA30x30}
475
+ ):
476
+ self._walls = value
477
+ else:
478
+ raise ValueError("Invalid shape for wall attachment")
479
+
480
+
481
+ class FixedSpeedDevice(BasePart, ABC):
482
+ """Device template for devices with speeds that can be set to Slow, Normal, or Fast"""
483
+
484
+ __slots__ = ("_speed",)
485
+
486
+ _speed: Speed
487
+
488
+ def __init__(
489
+ self,
490
+ x_pos: float,
491
+ y_pos: float,
492
+ z_pos: float,
493
+ x_rot: float,
494
+ y_rot: float,
495
+ z_rot: float,
496
+ *,
497
+ speed: Speed = Speed.NORMAL,
498
+ ) -> None:
499
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
500
+ self.speed = speed
501
+
502
+ def __repr__(self) -> str:
503
+ return (
504
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, speed={self.speed!r})"
505
+ if self.speed != Speed.NORMAL
506
+ else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
507
+ )
508
+
509
+ @property
510
+ def speed(self) -> Speed:
511
+ return self._speed
512
+
513
+ @speed.setter
514
+ def speed(self, value: Speed, /) -> None:
515
+ self._speed = value
516
+
517
+
518
+ class MovingCurve(FixedSpeedDevice):
519
+ __slots__ = ("_shape",)
520
+
521
+ _shape: Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL]
522
+
523
+ def __init__(
524
+ self,
525
+ x_pos: float,
526
+ y_pos: float,
527
+ z_pos: float,
528
+ x_rot: float,
529
+ y_rot: float,
530
+ z_rot: float,
531
+ *,
532
+ shape: Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL],
533
+ speed: Speed = Speed.NORMAL,
534
+ ) -> None:
535
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot, speed=speed)
536
+ self.shape = shape
537
+
538
+ @property
539
+ def cost(self) -> Literal[25]:
540
+ return 25
541
+
542
+ def __repr__(self) -> str:
543
+ return (
544
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r}, speed={self.speed!r})"
545
+ if self.speed != Speed.NORMAL
546
+ else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
547
+ )
548
+
549
+ @property
550
+ def shape(self) -> Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL]:
551
+ return self._shape
552
+
553
+ @shape.setter
554
+ def shape(
555
+ self, value: Literal[PartModel.CurveS, PartModel.CurveM, PartModel.CurveL], /
556
+ ) -> None:
557
+ self._shape = value
558
+
559
+
560
+ class SlidingTile(BasePart):
561
+ __slots__ = ()
562
+
563
+ @property
564
+ def cost(self) -> Literal[100]:
565
+ return 100
566
+
567
+
568
+ class ConveyorBelt(BasePart):
569
+ __slots__ = ("_reversing",)
570
+
571
+ _reversing: bool
572
+
573
+ def __init__(
574
+ self,
575
+ x_pos: float,
576
+ y_pos: float,
577
+ z_pos: float,
578
+ x_rot: float,
579
+ y_rot: float,
580
+ z_rot: float,
581
+ *,
582
+ reversing: bool = False,
583
+ ) -> None:
584
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
585
+ self.reversing = reversing
586
+
587
+ @property
588
+ def cost(self) -> Literal[60]:
589
+ return 60
590
+
591
+ def __repr__(self) -> str:
592
+ return (
593
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, reversing={self.reversing!r})"
594
+ if self.reversing
595
+ else super().__repr__()
596
+ )
597
+
598
+ @property
599
+ def reversing(self) -> bool:
600
+ return self._reversing
601
+
602
+ @reversing.setter
603
+ def reversing(self, value: bool, /) -> None:
604
+ self._reversing = value
605
+
606
+
607
+ class MagnetSegment(BasePart):
608
+ __slots__ = ("_shape",)
609
+
610
+ _shape: Literal[
611
+ DeviceModel.EndMagnet,
612
+ DeviceModel.StraightMagnet,
613
+ DeviceModel.CurveMagnetL,
614
+ DeviceModel.CurveMagnetS,
615
+ ]
616
+
617
+ def __init__(
618
+ self,
619
+ x_pos: float,
620
+ y_pos: float,
621
+ z_pos: float,
622
+ x_rot: float,
623
+ y_rot: float,
624
+ z_rot: float,
625
+ *,
626
+ shape: Literal[
627
+ DeviceModel.EndMagnet,
628
+ DeviceModel.StraightMagnet,
629
+ DeviceModel.CurveMagnetL,
630
+ DeviceModel.CurveMagnetS,
631
+ ],
632
+ ) -> None:
633
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
634
+ self.shape = shape
635
+
636
+ @property
637
+ def cost(self) -> Literal[15]:
638
+ return 15
639
+
640
+ def __repr__(self) -> str:
641
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
642
+
643
+ @property
644
+ def shape(self) -> Literal[
645
+ DeviceModel.EndMagnet,
646
+ DeviceModel.StraightMagnet,
647
+ DeviceModel.CurveMagnetL,
648
+ DeviceModel.CurveMagnetS,
649
+ ]:
650
+ return self._shape
651
+
652
+ @shape.setter
653
+ def shape(
654
+ self,
655
+ value: Literal[
656
+ DeviceModel.EndMagnet,
657
+ DeviceModel.StraightMagnet,
658
+ DeviceModel.CurveMagnetL,
659
+ DeviceModel.CurveMagnetS,
660
+ ],
661
+ /,
662
+ ) -> None:
663
+ self._shape = value
664
+
665
+
666
+ class Magnet(BasePart, MutableSequence[MagnetSegment]):
667
+ __slots__ = ("_segments",)
668
+
669
+ _segments: list[MagnetSegment]
670
+
671
+ def __init__(self, iterable: Iterable[MagnetSegment] = (), /) -> None:
672
+ self._segments = list(iterable)
673
+
674
+ def append(self, value: MagnetSegment) -> None:
675
+ return self._segments.append(value)
676
+
677
+ def clear(self) -> None:
678
+ self._segments.clear()
679
+
680
+ def __contains__(self, value: Any, /) -> bool:
681
+ return value in self._segments
682
+
683
+ @property
684
+ def cost(self) -> int:
685
+ return sum(segment.cost for segment in self)
686
+
687
+ def count(self, value: Any) -> int:
688
+ return self._segments.count(value)
689
+
690
+ def __delitem__(self, index: SupportsIndex | slice, /) -> None:
691
+ del self._segments[index]
692
+
693
+ def extend(self, iterable: Iterable[MagnetSegment], /) -> None:
694
+ self._segments.extend(iterable)
695
+
696
+ @overload
697
+ def __getitem__(self, index: SupportsIndex, /) -> MagnetSegment:
698
+ pass
699
+
700
+ @overload
701
+ def __getitem__(self, index: slice, /) -> MutableSequence[MagnetSegment]:
702
+ pass
703
+
704
+ def __getitem__(
705
+ self, index: SupportsIndex | slice, /
706
+ ) -> MagnetSegment | MutableSequence[MagnetSegment]:
707
+ return self._segments[index]
708
+
709
+ def __iadd__(self, value: Iterable[MagnetSegment], /) -> Self:
710
+ self._segments += value
711
+ return self
712
+
713
+ def index(
714
+ self, value: Any, start: SupportsIndex = 0, stop: SupportsIndex = maxsize, /
715
+ ) -> int:
716
+ return self._segments.index(value, start, stop)
717
+
718
+ def insert(self, index: SupportsIndex, value: MagnetSegment, /) -> None:
719
+ return self._segments.insert(index, value)
720
+
721
+ def __iter__(self) -> Iterator[MagnetSegment]:
722
+ return iter(self._segments)
723
+
724
+ def __len__(self) -> int:
725
+ return len(self._segments)
726
+
727
+ def pop(self, index: SupportsIndex = -1, /) -> MagnetSegment:
728
+ return self._segments.pop(index)
729
+
730
+ def remove(self, value: MagnetSegment, /) -> None:
731
+ self._segments.remove(value)
732
+
733
+ def __repr__(self) -> str:
734
+ return f"{type(self).__name__}({self._segments!r})"
735
+
736
+ def reverse(self) -> None:
737
+ self._segments.reverse()
738
+
739
+ def __reversed__(self) -> Iterator[MagnetSegment]:
740
+ return reversed(self._segments)
741
+
742
+ @overload
743
+ def __setitem__(self, index: SupportsIndex, value: MagnetSegment, /) -> None:
744
+ pass
745
+
746
+ @overload
747
+ def __setitem__(self, slice: slice, value: Iterable[MagnetSegment], /) -> None:
748
+ pass
749
+
750
+ def __setitem__(
751
+ self,
752
+ index: SupportsIndex | slice,
753
+ value: MagnetSegment | Iterable[MagnetSegment],
754
+ /,
755
+ ) -> None:
756
+ self._segments[index] = value # type: ignore
757
+
758
+ @property
759
+ def x_pos(self) -> float:
760
+ """Positive is right, negative is left"""
761
+ return self[-1].x_pos
762
+
763
+ @x_pos.setter
764
+ def x_pos(self, value: float, /) -> None:
765
+ offset: float = value - self.x_pos
766
+ for segment in self:
767
+ segment.x_pos += offset
768
+
769
+ @property
770
+ def x_rot(self) -> float:
771
+ """Represented in degrees from 0 to 360
772
+ Positive turns top face front, negative turns top face back
773
+ """
774
+ return self[-1].x_rot
775
+
776
+ @x_rot.setter
777
+ def x_rot(self, value: float, /) -> None:
778
+ """Positions of segments are not updated."""
779
+ offset: float = value - self.x_rot
780
+ for segment in self:
781
+ segment.x_rot += offset
782
+
783
+ @property
784
+ def y_pos(self) -> float:
785
+ """Positive is up, negative is down"""
786
+ return self[-1].y_pos
787
+
788
+ @y_pos.setter
789
+ def y_pos(self, value: float, /) -> None:
790
+ offset: float = value - self.y_pos
791
+ for segment in self:
792
+ segment.y_pos += offset
793
+
794
+ @property
795
+ def y_rot(self) -> float:
796
+ """Represented in degrees from 0 to 360
797
+ Positive turns front face right, negative turns front face left
798
+ """
799
+ return self[-1].y_rot
800
+
801
+ @y_rot.setter
802
+ def y_rot(self, value: float, /) -> None:
803
+ """Positions of segments are not updated."""
804
+ offset: float = value - self.y_rot
805
+ for segment in self:
806
+ segment.y_rot += offset
807
+
808
+ @property
809
+ def z_pos(self) -> float:
810
+ """Positive is front, negative is back"""
811
+ return self[-1].z_pos
812
+
813
+ @z_pos.setter
814
+ def z_pos(self, value: float, /) -> None:
815
+ offset: float = value - self.z_pos
816
+ for segment in self:
817
+ segment.z_pos += offset
818
+
819
+ @property
820
+ def z_rot(self) -> float:
821
+ """Represented in degrees from 0 to 360
822
+ Positive turns top face left, negative turns top face right
823
+ """
824
+ return self[-1].z_rot
825
+
826
+ @z_rot.setter
827
+ def z_rot(self, value: float, /) -> None:
828
+ """Positions of segments are not updated."""
829
+ offset: float = value - self.z_rot
830
+ for segment in self:
831
+ segment.z_rot += offset
832
+
833
+
834
+ class DashTunnel(BasePart):
835
+ __slots__ = ("_shape",)
836
+
837
+ _shape: Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB]
838
+
839
+ def __init__(
840
+ self,
841
+ x_pos: float,
842
+ y_pos: float,
843
+ z_pos: float,
844
+ x_rot: float,
845
+ y_rot: float,
846
+ z_rot: float,
847
+ *,
848
+ shape: Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB],
849
+ ) -> None:
850
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
851
+ self.shape = shape
852
+
853
+ @property
854
+ def cost(self) -> Literal[35, 100]:
855
+ match self.shape:
856
+ case DeviceModel.DashTunnelA:
857
+ return 35
858
+ case DeviceModel.DashTunnelB:
859
+ return 100
860
+
861
+ def __repr__(self) -> str:
862
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
863
+
864
+ @property
865
+ def shape(self) -> Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB]:
866
+ return self._shape
867
+
868
+ @shape.setter
869
+ def shape(
870
+ self, value: Literal[DeviceModel.DashTunnelA, DeviceModel.DashTunnelB], /
871
+ ) -> None:
872
+ self._shape = value
873
+
874
+
875
+ class SeesawBlock(BasePart):
876
+ __slots__ = ("_auto", "_shape")
877
+
878
+ _auto: bool
879
+ _shape: Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock]
880
+
881
+ def __init__(
882
+ self,
883
+ x_pos: float,
884
+ y_pos: float,
885
+ z_pos: float,
886
+ x_rot: float,
887
+ y_rot: float,
888
+ z_rot: float,
889
+ *,
890
+ auto: bool = False,
891
+ shape: Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock],
892
+ ) -> None:
893
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
894
+ self.auto = auto
895
+ self.shape = shape
896
+
897
+ @property
898
+ def auto(self) -> bool:
899
+ return self._auto
900
+
901
+ @auto.setter
902
+ def auto(self, value: bool, /) -> None:
903
+ self._auto = value
904
+
905
+ @property
906
+ def cost(self) -> Literal[100]:
907
+ return 100
908
+
909
+ def __repr__(self) -> str:
910
+ return (
911
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, auto={self.auto!r}, shape={self.shape!r})"
912
+ if self.auto
913
+ else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
914
+ )
915
+
916
+ @property
917
+ def shape(self) -> Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock]:
918
+ return self._shape
919
+
920
+ @shape.setter
921
+ def shape(
922
+ self, value: Literal[DeviceModel.SeesawLBlock, DeviceModel.SeesawIBlock], /
923
+ ) -> None:
924
+ self._shape = value
925
+
926
+
927
+ class Cannon(BasePart):
928
+ __slots__ = ()
929
+
930
+ @property
931
+ def cost(self) -> Literal[30]:
932
+ return 30
933
+
934
+
935
+ class Drawbridge(BasePart):
936
+ __slots__ = ()
937
+
938
+ @property
939
+ def cost(self) -> Literal[50]:
940
+ return 50
941
+
942
+
943
+ class Turntable(FixedSpeedDevice):
944
+ __slots__ = ()
945
+
946
+ @property
947
+ def cost(self) -> Literal[50]:
948
+ return 50
949
+
950
+
951
+ class Bumper(BasePart):
952
+ __slots__ = ("_powerful",)
953
+
954
+ _powerful: bool
955
+
956
+ def __init__(
957
+ self,
958
+ x_pos: float,
959
+ y_pos: float,
960
+ z_pos: float,
961
+ x_rot: float,
962
+ y_rot: float,
963
+ z_rot: float,
964
+ *,
965
+ powerful: bool = False,
966
+ ) -> None:
967
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
968
+ self.powerful = powerful
969
+
970
+ @property
971
+ def cost(self) -> Literal[20]:
972
+ return 20
973
+
974
+ @property
975
+ def powerful(self) -> bool:
976
+ return self._powerful
977
+
978
+ @powerful.setter
979
+ def powerful(self, value: bool, /) -> None:
980
+ self._powerful = value
981
+
982
+ def __repr__(self) -> str:
983
+ return (
984
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, powerful={self.powerful!r})"
985
+ if self.powerful
986
+ else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
987
+ )
988
+
989
+
990
+ class Thorn(BasePart):
991
+ __slots__ = ()
992
+
993
+ @property
994
+ def cost(self) -> Literal[35]:
995
+ return 35
996
+
997
+
998
+ class Gear(FixedSpeedDevice):
999
+ __slots__ = ()
1000
+
1001
+ @property
1002
+ def cost(self) -> Literal[50]:
1003
+ return 50
1004
+
1005
+
1006
+ class Fan(BasePart):
1007
+ __slots__ = ("_wind_pattern",)
1008
+
1009
+ _wind_pattern: Literal[
1010
+ DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan
1011
+ ]
1012
+
1013
+ def __init__(
1014
+ self,
1015
+ x_pos: float,
1016
+ y_pos: float,
1017
+ z_pos: float,
1018
+ x_rot: float,
1019
+ y_rot: float,
1020
+ z_rot: float,
1021
+ *,
1022
+ wind_pattern: Literal[
1023
+ DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan
1024
+ ] = DeviceModel.Fan,
1025
+ ) -> None:
1026
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1027
+ self.wind_pattern = wind_pattern
1028
+
1029
+ @property
1030
+ def cost(self) -> Literal[50]:
1031
+ return 50
1032
+
1033
+ def __repr__(self) -> str:
1034
+ return (
1035
+ f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, wind_pattern={self.wind_pattern!r})"
1036
+ if self.wind_pattern != DeviceModel.Fan
1037
+ else f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r})"
1038
+ )
1039
+
1040
+ @property
1041
+ def wind_pattern(
1042
+ self,
1043
+ ) -> Literal[DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan]:
1044
+ return self._wind_pattern
1045
+
1046
+ @wind_pattern.setter
1047
+ def wind_pattern(
1048
+ self,
1049
+ value: Literal[DeviceModel.Fan, DeviceModel.PowerfulFan, DeviceModel.TimerFan],
1050
+ /,
1051
+ ) -> None:
1052
+ self._wind_pattern = value
1053
+
1054
+
1055
+ class Spring(BasePart):
1056
+ __slots__ = ()
1057
+
1058
+ @property
1059
+ def cost(self) -> Literal[25]:
1060
+ return 25
1061
+
1062
+
1063
+ @unique
1064
+ class MovementTiming(Enum):
1065
+ A = 23
1066
+ B = 39
1067
+ C = 55
1068
+
1069
+
1070
+ class TimedDevice(BasePart, ABC):
1071
+ """Device Template for devices which can have one of three timings for their movements"""
1072
+
1073
+ __slots__ = ("_timing",)
1074
+
1075
+ _timing: MovementTiming
1076
+
1077
+ def __init__(
1078
+ self,
1079
+ x_pos: float,
1080
+ y_pos: float,
1081
+ z_pos: float,
1082
+ x_rot: float,
1083
+ y_rot: float,
1084
+ z_rot: float,
1085
+ *,
1086
+ timing: MovementTiming,
1087
+ ) -> None:
1088
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1089
+ self.timing = timing
1090
+
1091
+ def __repr__(self) -> str:
1092
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, timing={self.timing!r})"
1093
+
1094
+ @property
1095
+ def timing(self) -> MovementTiming:
1096
+ return self._timing
1097
+
1098
+ @timing.setter
1099
+ def timing(self, value: MovementTiming, /) -> None:
1100
+ self._timing = value
1101
+
1102
+
1103
+ class Punch(TimedDevice):
1104
+ __slots__ = ()
1105
+
1106
+ @property
1107
+ def cost(self) -> Literal[30]:
1108
+ return 30
1109
+
1110
+
1111
+ class Press(TimedDevice):
1112
+ __slots__ = ()
1113
+
1114
+ @property
1115
+ def cost(self) -> Literal[50]:
1116
+ return 50
1117
+
1118
+
1119
+ class Scissors(TimedDevice):
1120
+ __slots__ = ()
1121
+
1122
+ @property
1123
+ def cost(self) -> Literal[70]:
1124
+ return 70
1125
+
1126
+
1127
+ class MagnifyingGlass(BasePart):
1128
+ __slots__ = ()
1129
+
1130
+ @property
1131
+ def cost(self) -> Literal[200]:
1132
+ return 200
1133
+
1134
+
1135
+ class UpsideDownStageDevice(BasePart):
1136
+ __slots__ = ()
1137
+
1138
+ @property
1139
+ def cost(self) -> Literal[50]:
1140
+ return 50
1141
+
1142
+
1143
+ class UpsideDownBall(BasePart):
1144
+ __slots__ = ()
1145
+
1146
+ @property
1147
+ def cost(self) -> Literal[25]:
1148
+ return 25
1149
+
1150
+
1151
+ class SizeTunnel(BasePart):
1152
+ __slots__ = ("_size",)
1153
+
1154
+ _size: Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel]
1155
+
1156
+ def __init__(
1157
+ self,
1158
+ x_pos: float,
1159
+ y_pos: float,
1160
+ z_pos: float,
1161
+ x_rot: float,
1162
+ y_rot: float,
1163
+ z_rot: float,
1164
+ *,
1165
+ size: Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel],
1166
+ ) -> None:
1167
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1168
+ self.size = size
1169
+
1170
+ @property
1171
+ def cost(self) -> Literal[50]:
1172
+ return 50
1173
+
1174
+ def __repr__(self) -> str:
1175
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, size={self.size!r})"
1176
+
1177
+ @property
1178
+ def size(self) -> Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel]:
1179
+ return self._size
1180
+
1181
+ @size.setter
1182
+ def size(
1183
+ self, value: Literal[DeviceModel.SmallTunnel, DeviceModel.BigTunnel], /
1184
+ ) -> None:
1185
+ self._size = value
1186
+
1187
+
1188
+ class TrainTrack(BasePart):
1189
+ __slots__ = ("_shape",)
1190
+
1191
+ _shape: Literal[
1192
+ DeviceModel.EndTracks,
1193
+ DeviceModel.LeftTracks,
1194
+ DeviceModel.RightTracks,
1195
+ DeviceModel.StraightTracks,
1196
+ ]
1197
+
1198
+ def __init__(
1199
+ self,
1200
+ x_pos: float,
1201
+ y_pos: float,
1202
+ z_pos: float,
1203
+ x_rot: float,
1204
+ y_rot: float,
1205
+ z_rot: float,
1206
+ *,
1207
+ shape: Literal[
1208
+ DeviceModel.EndTracks,
1209
+ DeviceModel.LeftTracks,
1210
+ DeviceModel.RightTracks,
1211
+ DeviceModel.StraightTracks,
1212
+ ],
1213
+ ) -> None:
1214
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1215
+ self.shape = shape
1216
+
1217
+ @property
1218
+ def cost(self) -> Literal[0, 20]:
1219
+ return 0 if self.shape is DeviceModel.EndTracks else 20
1220
+
1221
+ def __repr__(self) -> str:
1222
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, shape={self.shape!r})"
1223
+
1224
+ @property
1225
+ def shape(
1226
+ self,
1227
+ ) -> Literal[
1228
+ DeviceModel.EndTracks,
1229
+ DeviceModel.LeftTracks,
1230
+ DeviceModel.RightTracks,
1231
+ DeviceModel.StraightTracks,
1232
+ ]:
1233
+ return self._shape
1234
+
1235
+ @shape.setter
1236
+ def shape(
1237
+ self,
1238
+ value: Literal[
1239
+ DeviceModel.EndTracks,
1240
+ DeviceModel.LeftTracks,
1241
+ DeviceModel.RightTracks,
1242
+ DeviceModel.StraightTracks,
1243
+ ],
1244
+ /,
1245
+ ) -> None:
1246
+ self._shape = value
1247
+
1248
+
1249
+ class ToyTrain(BasePart, MutableSequence[TrainTrack]):
1250
+ __slots__ = ("_tracks",)
1251
+
1252
+ _tracks: list[TrainTrack]
1253
+
1254
+ def __init__(
1255
+ self,
1256
+ x_pos: float,
1257
+ y_pos: float,
1258
+ z_pos: float,
1259
+ x_rot: float,
1260
+ y_rot: float,
1261
+ z_rot: float,
1262
+ *,
1263
+ tracks: Iterable[TrainTrack] = (),
1264
+ ) -> None:
1265
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1266
+ self._tracks = list(tracks)
1267
+
1268
+ def append(self, value: TrainTrack) -> None:
1269
+ return self._tracks.append(value)
1270
+
1271
+ def clear(self) -> None:
1272
+ self._tracks.clear()
1273
+
1274
+ def __contains__(self, value: Any, /) -> bool:
1275
+ return value in self._tracks
1276
+
1277
+ @property
1278
+ def cost(self) -> int:
1279
+ return 100 + sum(track.cost for track in self)
1280
+
1281
+ def count(self, value: Any) -> int:
1282
+ return self._tracks.count(value)
1283
+
1284
+ def __delitem__(self, index: SupportsIndex | slice, /) -> None:
1285
+ del self._tracks[index]
1286
+
1287
+ def extend(self, iterable: Iterable[TrainTrack], /) -> None:
1288
+ self._tracks.extend(iterable)
1289
+
1290
+ @overload
1291
+ def __getitem__(self, index: SupportsIndex, /) -> TrainTrack:
1292
+ pass
1293
+
1294
+ @overload
1295
+ def __getitem__(self, index: slice, /) -> MutableSequence[TrainTrack]:
1296
+ pass
1297
+
1298
+ def __getitem__(
1299
+ self, index: SupportsIndex | slice, /
1300
+ ) -> TrainTrack | MutableSequence[TrainTrack]:
1301
+ return self._tracks[index]
1302
+
1303
+ def __iadd__(self, value: Iterable[TrainTrack], /) -> Self:
1304
+ self._tracks += value
1305
+ return self
1306
+
1307
+ def index(
1308
+ self, value: Any, start: SupportsIndex = 0, stop: SupportsIndex = maxsize, /
1309
+ ) -> int:
1310
+ return self._tracks.index(value, start, stop)
1311
+
1312
+ def insert(self, index: SupportsIndex, value: TrainTrack, /) -> None:
1313
+ return self._tracks.insert(index, value)
1314
+
1315
+ def __iter__(self) -> Iterator[TrainTrack]:
1316
+ return iter(self._tracks)
1317
+
1318
+ def __len__(self) -> int:
1319
+ return len(self._tracks)
1320
+
1321
+ def pop(self, index: SupportsIndex = -1, /) -> TrainTrack:
1322
+ return self._tracks.pop(index)
1323
+
1324
+ def remove(self, value: TrainTrack, /) -> None:
1325
+ self._tracks.remove(value)
1326
+
1327
+ def __repr__(self) -> str:
1328
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, tracks={self._tracks!r})"
1329
+
1330
+ def reverse(self) -> None:
1331
+ self._tracks.reverse()
1332
+
1333
+ def __reversed__(self) -> Iterator[TrainTrack]:
1334
+ return reversed(self._tracks)
1335
+
1336
+ @overload
1337
+ def __setitem__(self, index: SupportsIndex, value: TrainTrack, /) -> None:
1338
+ pass
1339
+
1340
+ @overload
1341
+ def __setitem__(self, slice: slice, value: Iterable[TrainTrack], /) -> None:
1342
+ pass
1343
+
1344
+ def __setitem__(
1345
+ self, index: SupportsIndex | slice, value: TrainTrack | Iterable[TrainTrack], /
1346
+ ) -> None:
1347
+ self._tracks[index] = value # type: ignore
1348
+
1349
+
1350
+ class Warp(BasePart):
1351
+ __slots__ = (
1352
+ "_dest_x",
1353
+ "_dest_y",
1354
+ "_dest_z",
1355
+ "_return_dest_x",
1356
+ "_return_dest_y",
1357
+ "_return_dest_z",
1358
+ "_return_x_pos",
1359
+ "_return_x_rot",
1360
+ "_return_y_pos",
1361
+ "_return_y_rot",
1362
+ "_return_z_pos",
1363
+ "_return_z_rot",
1364
+ )
1365
+
1366
+ _dest_x: float
1367
+ _dest_y: float
1368
+ _dest_z: float
1369
+ _return_dest_x: float
1370
+ _return_dest_y: float
1371
+ _return_dest_z: float
1372
+ _return_x_pos: float
1373
+ _return_x_rot: float
1374
+ _return_y_pos: float
1375
+ _return_y_rot: float
1376
+ _return_z_pos: float
1377
+ _return_z_rot: float
1378
+
1379
+ def __init__(
1380
+ self,
1381
+ x_pos: float,
1382
+ y_pos: float,
1383
+ z_pos: float,
1384
+ x_rot: float,
1385
+ y_rot: float,
1386
+ z_rot: float,
1387
+ *,
1388
+ dest_x: float,
1389
+ dest_y: float,
1390
+ dest_z: float,
1391
+ return_x_pos: float,
1392
+ return_y_pos: float,
1393
+ return_z_pos: float,
1394
+ return_x_rot: float,
1395
+ return_y_rot: float,
1396
+ return_z_rot: float,
1397
+ return_dest_x: float,
1398
+ return_dest_y: float,
1399
+ return_dest_z: float,
1400
+ ) -> None:
1401
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1402
+ self.dest_x = dest_x
1403
+ self.dest_y = dest_y
1404
+ self.dest_z = dest_z
1405
+ self.return_dest_x = return_dest_x
1406
+ self.return_dest_y = return_dest_y
1407
+ self.return_dest_z = return_dest_z
1408
+ self.return_x_pos = return_x_pos
1409
+ self.return_x_rot = return_x_rot
1410
+ self.return_y_pos = return_y_pos
1411
+ self.return_y_rot = return_y_rot
1412
+ self.return_z_pos = return_z_pos
1413
+ self.return_z_rot = return_z_rot
1414
+
1415
+ @property
1416
+ def cost(self) -> Literal[25]:
1417
+ return 25
1418
+
1419
+ @property
1420
+ def dest_x(self) -> float:
1421
+ """Positive is right, negative is left"""
1422
+ return self._dest_x
1423
+
1424
+ @dest_x.setter
1425
+ def dest_x(self, value: float, /) -> None:
1426
+ self._dest_x = value
1427
+
1428
+ @property
1429
+ def dest_y(self) -> float:
1430
+ return self._dest_y
1431
+
1432
+ @dest_y.setter
1433
+ def dest_y(self, value: float, /) -> None:
1434
+ """Positive is up, negative is down"""
1435
+ self._dest_y = value
1436
+
1437
+ @property
1438
+ def dest_z(self) -> float:
1439
+ """Positive is front, negative is back"""
1440
+ return self._dest_z
1441
+
1442
+ @dest_z.setter
1443
+ def dest_z(self, value: float, /) -> None:
1444
+ self._dest_z = value
1445
+
1446
+ def __repr__(self) -> str:
1447
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, dest_x={self.dest_x!r}, dest_y={self.dest_y!r}, dest_z={self.dest_z!r}, return_x_pos={self.return_x_pos}, return_y_pos={self.return_y_pos}, return_z_pos={self.return_z_pos}, return_x_rot={self.return_x_rot}, return_y_rot={self.return_y_rot}, return_z_rot={self.return_z_rot}, return_dest_x={self.return_dest_x}, return_dest_y={self.return_dest_y}, return_dest_z={self.return_dest_z})"
1448
+
1449
+ @property
1450
+ def return_dest_x(self) -> float:
1451
+ """Positive is right, negative is left"""
1452
+ return self._return_dest_x
1453
+
1454
+ @return_dest_x.setter
1455
+ def return_dest_x(self, value: float, /) -> None:
1456
+ self._return_dest_x = value
1457
+
1458
+ @property
1459
+ def return_dest_y(self) -> float:
1460
+ return self._return_dest_y
1461
+
1462
+ @return_dest_y.setter
1463
+ def return_dest_y(self, value: float, /) -> None:
1464
+ """Positive is up, negative is down"""
1465
+ self._return_dest_y = value
1466
+
1467
+ @property
1468
+ def return_dest_z(self) -> float:
1469
+ """Positive is front, negative is back"""
1470
+ return self._return_dest_z
1471
+
1472
+ @return_dest_z.setter
1473
+ def return_dest_z(self, value: float, /) -> None:
1474
+ self._return_dest_z = value
1475
+
1476
+ @property
1477
+ def return_x_pos(self) -> float:
1478
+ """Positive is right, negative is left"""
1479
+ return self._return_x_pos
1480
+
1481
+ @return_x_pos.setter
1482
+ def return_x_pos(self, value: float, /) -> None:
1483
+ self._return_x_pos = value
1484
+
1485
+ @property
1486
+ def return_x_rot(self) -> float:
1487
+ """Represented in degrees from 0 to 360
1488
+ Positive turns top face front, negative turns top face back
1489
+ """
1490
+ return self._return_x_rot
1491
+
1492
+ @return_x_rot.setter
1493
+ def return_x_rot(self, value: float, /) -> None:
1494
+ self._return_x_rot = value % 360
1495
+
1496
+ @property
1497
+ def return_y_pos(self) -> float:
1498
+ """Positive is up, negative is down"""
1499
+ return self._return_y_pos
1500
+
1501
+ @return_y_pos.setter
1502
+ def return_y_pos(self, value: float, /) -> None:
1503
+ self._return_y_pos = value
1504
+
1505
+ @property
1506
+ def return_y_rot(self) -> float:
1507
+ """Represented in degrees from 0 to 360
1508
+ Positive turns front face right, negative turns front face left
1509
+ """
1510
+ return self._return_y_rot
1511
+
1512
+ @return_y_rot.setter
1513
+ def return_y_rot(self, value: float, /) -> None:
1514
+ self._return_y_rot = value % 360
1515
+
1516
+ @property
1517
+ def return_z_pos(self) -> float:
1518
+ """Positive is front, negative is back"""
1519
+ return self._return_z_pos
1520
+
1521
+ @return_z_pos.setter
1522
+ def return_z_pos(self, value: float, /) -> None:
1523
+ self._return_z_pos = value
1524
+
1525
+ @property
1526
+ def return_z_rot(self) -> float:
1527
+ """Represented in degrees from 0 to 360
1528
+ Positive turns top face left, negative turns top face right
1529
+ """
1530
+ return self._return_z_rot
1531
+
1532
+ @return_z_rot.setter
1533
+ def return_z_rot(self, value: float, /) -> None:
1534
+ self._return_z_rot = value % 360
1535
+
1536
+
1537
+ class BlinkingTile(TimedDevice):
1538
+ __slots__ = ()
1539
+
1540
+ @property
1541
+ def cost(self) -> Literal[20]:
1542
+ return 20
1543
+
1544
+
1545
+ class MelodyTile(BasePart):
1546
+ __slots__ = ("_note",)
1547
+
1548
+ _note: Literal[
1549
+ DeviceModel.MelodyTileLowG,
1550
+ DeviceModel.MelodyTileLowGSharp,
1551
+ DeviceModel.MelodyTileLowA,
1552
+ DeviceModel.MelodyTileLowASharp,
1553
+ DeviceModel.MelodyTileLowB,
1554
+ DeviceModel.MelodyTileC,
1555
+ DeviceModel.MelodyTileCSharp,
1556
+ DeviceModel.MelodyTileD,
1557
+ DeviceModel.MelodyTileDSharp,
1558
+ DeviceModel.MelodyTileE,
1559
+ DeviceModel.MelodyTileF,
1560
+ DeviceModel.MelodyTileFSharp,
1561
+ DeviceModel.MelodyTileG,
1562
+ DeviceModel.MelodyTileGSharp,
1563
+ DeviceModel.MelodyTileA,
1564
+ DeviceModel.MelodyTileASharp,
1565
+ DeviceModel.MelodyTileB,
1566
+ DeviceModel.MelodyTileHighC,
1567
+ DeviceModel.MelodyTileHighCSharp,
1568
+ DeviceModel.MelodyTileHighD,
1569
+ DeviceModel.MelodyTileHighDSharp,
1570
+ DeviceModel.MelodyTileHighE,
1571
+ DeviceModel.MelodyTileHighF,
1572
+ DeviceModel.MelodyTileHighFSharp,
1573
+ DeviceModel.MelodyTileHighG,
1574
+ ]
1575
+
1576
+ def __init__(
1577
+ self,
1578
+ x_pos: float,
1579
+ y_pos: float,
1580
+ z_pos: float,
1581
+ x_rot: float,
1582
+ y_rot: float,
1583
+ z_rot: float,
1584
+ *,
1585
+ note: Literal[
1586
+ DeviceModel.MelodyTileLowG,
1587
+ DeviceModel.MelodyTileLowGSharp,
1588
+ DeviceModel.MelodyTileLowA,
1589
+ DeviceModel.MelodyTileLowASharp,
1590
+ DeviceModel.MelodyTileLowB,
1591
+ DeviceModel.MelodyTileC,
1592
+ DeviceModel.MelodyTileCSharp,
1593
+ DeviceModel.MelodyTileD,
1594
+ DeviceModel.MelodyTileDSharp,
1595
+ DeviceModel.MelodyTileE,
1596
+ DeviceModel.MelodyTileF,
1597
+ DeviceModel.MelodyTileFSharp,
1598
+ DeviceModel.MelodyTileG,
1599
+ DeviceModel.MelodyTileGSharp,
1600
+ DeviceModel.MelodyTileA,
1601
+ DeviceModel.MelodyTileASharp,
1602
+ DeviceModel.MelodyTileB,
1603
+ DeviceModel.MelodyTileHighC,
1604
+ DeviceModel.MelodyTileHighCSharp,
1605
+ DeviceModel.MelodyTileHighD,
1606
+ DeviceModel.MelodyTileHighDSharp,
1607
+ DeviceModel.MelodyTileHighE,
1608
+ DeviceModel.MelodyTileHighF,
1609
+ DeviceModel.MelodyTileHighFSharp,
1610
+ DeviceModel.MelodyTileHighG,
1611
+ ],
1612
+ ) -> None:
1613
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1614
+ self.note = note
1615
+
1616
+ @property
1617
+ def cost(self) -> Literal[20]:
1618
+ return 20
1619
+
1620
+ @property
1621
+ def note(self) -> Literal[
1622
+ DeviceModel.MelodyTileLowG,
1623
+ DeviceModel.MelodyTileLowGSharp,
1624
+ DeviceModel.MelodyTileLowA,
1625
+ DeviceModel.MelodyTileLowASharp,
1626
+ DeviceModel.MelodyTileLowB,
1627
+ DeviceModel.MelodyTileC,
1628
+ DeviceModel.MelodyTileCSharp,
1629
+ DeviceModel.MelodyTileD,
1630
+ DeviceModel.MelodyTileDSharp,
1631
+ DeviceModel.MelodyTileE,
1632
+ DeviceModel.MelodyTileF,
1633
+ DeviceModel.MelodyTileFSharp,
1634
+ DeviceModel.MelodyTileG,
1635
+ DeviceModel.MelodyTileGSharp,
1636
+ DeviceModel.MelodyTileA,
1637
+ DeviceModel.MelodyTileASharp,
1638
+ DeviceModel.MelodyTileB,
1639
+ DeviceModel.MelodyTileHighC,
1640
+ DeviceModel.MelodyTileHighCSharp,
1641
+ DeviceModel.MelodyTileHighD,
1642
+ DeviceModel.MelodyTileHighDSharp,
1643
+ DeviceModel.MelodyTileHighE,
1644
+ DeviceModel.MelodyTileHighF,
1645
+ DeviceModel.MelodyTileHighFSharp,
1646
+ DeviceModel.MelodyTileHighG,
1647
+ ]:
1648
+ return self._note
1649
+
1650
+ @note.setter
1651
+ def note(
1652
+ self,
1653
+ value: Literal[
1654
+ DeviceModel.MelodyTileLowG,
1655
+ DeviceModel.MelodyTileLowGSharp,
1656
+ DeviceModel.MelodyTileLowA,
1657
+ DeviceModel.MelodyTileLowASharp,
1658
+ DeviceModel.MelodyTileLowB,
1659
+ DeviceModel.MelodyTileC,
1660
+ DeviceModel.MelodyTileCSharp,
1661
+ DeviceModel.MelodyTileD,
1662
+ DeviceModel.MelodyTileDSharp,
1663
+ DeviceModel.MelodyTileE,
1664
+ DeviceModel.MelodyTileF,
1665
+ DeviceModel.MelodyTileFSharp,
1666
+ DeviceModel.MelodyTileG,
1667
+ DeviceModel.MelodyTileGSharp,
1668
+ DeviceModel.MelodyTileA,
1669
+ DeviceModel.MelodyTileASharp,
1670
+ DeviceModel.MelodyTileB,
1671
+ DeviceModel.MelodyTileHighC,
1672
+ DeviceModel.MelodyTileHighCSharp,
1673
+ DeviceModel.MelodyTileHighD,
1674
+ DeviceModel.MelodyTileHighDSharp,
1675
+ DeviceModel.MelodyTileHighE,
1676
+ DeviceModel.MelodyTileHighF,
1677
+ DeviceModel.MelodyTileHighFSharp,
1678
+ DeviceModel.MelodyTileHighG,
1679
+ ],
1680
+ /,
1681
+ ) -> None:
1682
+ self._note = value
1683
+
1684
+ def __repr__(self) -> str:
1685
+ return f"{type(self).__name__}({self.x_pos!r}, {self.y_pos!r}, {self.z_pos!r}, {self.x_rot!r}, {self.y_rot!r}, {self.z_rot!r}, note={self.note!r})"
1686
+
1687
+
1688
+ class TextBox(BasePart):
1689
+ __slots__ = ("_shape", "_text_id")
1690
+
1691
+ _shape: Literal[DeviceModel.CubicTextBox, DeviceModel.WallTextBox]
1692
+ _text_id: int
1693
+
1694
+ def __init__(
1695
+ self,
1696
+ x_pos: float,
1697
+ y_pos: float,
1698
+ z_pos: float,
1699
+ x_rot: float,
1700
+ y_rot: float,
1701
+ z_rot: float,
1702
+ *,
1703
+ shape: Literal[DeviceModel.CubicTextBox, DeviceModel.WallTextBox],
1704
+ text_id: SupportsIndex,
1705
+ ) -> None:
1706
+ super().__init__(x_pos, y_pos, z_pos, x_rot, y_rot, z_rot)
1707
+ self.shape = shape
1708
+ self.text_id = text_id # type: ignore[assignment]
1709
+
1710
+ @property
1711
+ def cost(self) -> Literal[0]:
1712
+ return 0
1713
+
1714
+ @property
1715
+ def shape(self) -> Literal[DeviceModel.CubicTextBox, DeviceModel.WallTextBox]:
1716
+ return self._shape
1717
+
1718
+ @shape.setter
1719
+ def shape(
1720
+ self, value: Literal[DeviceModel.CubicTextBox, DeviceModel.WallTextBox], /
1721
+ ) -> None:
1722
+ self._shape = value
1723
+
1724
+ @property
1725
+ def text_id(self) -> int:
1726
+ return self._text_id
1727
+
1728
+ @text_id.setter
1729
+ def text_id(self, value: SupportsIndex, /) -> None:
1730
+ self._text_id = index(value)
1731
+
1732
+
1733
+ class KororinCapsule(BasePart):
1734
+ __slots__ = ()
1735
+
1736
+ @property
1737
+ def cost(self) -> Literal[0]:
1738
+ return 0
1739
+
1740
+
1741
+ class GreenCrystal(BasePart):
1742
+ __slots__ = ()
1743
+
1744
+ @property
1745
+ def cost(self) -> Literal[0]:
1746
+ return 0
1747
+
1748
+
1749
+ class Ant(BasePart):
1750
+ __slots__ = ()
1751
+
1752
+ @property
1753
+ def cost(self) -> Literal[0]:
1754
+ return 0