2024-04-26 19:48:30 +00:00
|
|
|
"""
|
|
|
|
Class to draw lines.
|
|
|
|
|
|
|
|
This is the most well-documented version of a concrete doodle,
|
|
|
|
the easiest to learn from.
|
|
|
|
"""
|
2024-04-22 07:12:59 +00:00
|
|
|
import math
|
|
|
|
import random
|
2024-04-26 21:31:12 +00:00
|
|
|
from typing import Callable, Self
|
2024-04-22 07:12:59 +00:00
|
|
|
from .doodles import Doodle
|
2024-04-24 02:26:34 +00:00
|
|
|
from .world import world
|
2024-04-22 07:12:59 +00:00
|
|
|
|
2024-04-22 07:35:23 +00:00
|
|
|
|
2024-04-22 07:12:59 +00:00
|
|
|
class Line(Doodle):
|
2024-04-26 19:48:30 +00:00
|
|
|
# Adds one attribute to Doodle: the distance/offset vector
|
|
|
|
# from the position. Together these form the end points of the
|
|
|
|
# line.
|
|
|
|
_offset_vec: tuple[float, float]
|
|
|
|
|
2024-04-22 07:12:59 +00:00
|
|
|
def __init__(self, parent=None):
|
|
|
|
"""
|
|
|
|
We keep the same interface as Doodle, to follow the Liskov substitution
|
|
|
|
principle.
|
|
|
|
|
|
|
|
We could add more *optional* arguments, but no more required ones
|
|
|
|
than the parent class.
|
|
|
|
"""
|
|
|
|
super().__init__(parent)
|
|
|
|
# a line is stored as a position (on the parent class)
|
|
|
|
# and an offset vector
|
|
|
|
self._offset_vec = (10, 0)
|
|
|
|
|
|
|
|
def __repr__(self):
|
2024-04-23 03:54:18 +00:00
|
|
|
return f"Line(pos={self.world_vec}, end={self.end_vec}, {self._color})"
|
2024-04-22 07:12:59 +00:00
|
|
|
|
2024-04-24 02:26:34 +00:00
|
|
|
def draw(self):
|
2024-04-22 07:12:59 +00:00
|
|
|
"""
|
|
|
|
Implementation of the abstract draw function for the line.
|
|
|
|
|
2024-04-26 19:48:30 +00:00
|
|
|
This class passes the responsibility of actually drawing to
|
|
|
|
the world.draw_engine, which is designed to be configurable.
|
2024-04-22 07:12:59 +00:00
|
|
|
|
2024-04-26 19:48:30 +00:00
|
|
|
An earlier version had Pygame drawing code in here, but that
|
|
|
|
violated the Single Responsibility Principle.
|
2024-04-22 07:12:59 +00:00
|
|
|
|
2024-04-26 19:48:30 +00:00
|
|
|
Instead, as a pass-through, the actual drawing logic is not coupled
|
|
|
|
to the mathematical representation of a line.
|
2024-04-22 07:12:59 +00:00
|
|
|
"""
|
2024-04-24 02:26:34 +00:00
|
|
|
world.draw_engine.line_draw(self)
|
2024-04-22 07:12:59 +00:00
|
|
|
|
2024-04-26 19:48:30 +00:00
|
|
|
## Setters / Modifiers / Getters ##############
|
|
|
|
|
2024-04-26 21:31:12 +00:00
|
|
|
def to(self, x: float, y: float) -> Self:
|
2024-04-22 07:12:59 +00:00
|
|
|
"""
|
|
|
|
A setter for the line's offset vector.
|
|
|
|
|
|
|
|
Example usage:
|
|
|
|
|
|
|
|
Line().pos(10, 10).to(50, 50)
|
|
|
|
|
|
|
|
Makes a line from (10, 10) to (50, 50).
|
|
|
|
"""
|
|
|
|
self._offset_vec = (x, y)
|
|
|
|
return self
|
|
|
|
|
2024-04-26 21:31:12 +00:00
|
|
|
def vec(self, degrees: float | Callable, magnitude: float):
|
2024-04-22 07:12:59 +00:00
|
|
|
"""
|
2024-04-26 19:48:30 +00:00
|
|
|
Alternate setter, to create offset vector from angle & length.
|
|
|
|
|
|
|
|
This is similar to the constructor/alternate constructor concept
|
|
|
|
where there's a base constructor that sets the propertries
|
|
|
|
directly (`to`), but there is also an alternate option
|
|
|
|
that handles commonly used case.
|
2024-04-22 07:12:59 +00:00
|
|
|
"""
|
2024-04-26 21:31:12 +00:00
|
|
|
if callable(degrees):
|
2024-04-22 07:35:23 +00:00
|
|
|
self.register_update(
|
|
|
|
self.to,
|
|
|
|
lambda: magnitude * math.cos(math.radians(degrees())),
|
|
|
|
lambda: magnitude * math.sin(math.radians(degrees())),
|
|
|
|
)
|
|
|
|
return self
|
|
|
|
|
2024-04-22 07:12:59 +00:00
|
|
|
return self.to(
|
|
|
|
magnitude * math.cos(math.radians(degrees)),
|
|
|
|
magnitude * math.sin(math.radians(degrees)),
|
|
|
|
)
|
|
|
|
|
2024-04-26 21:31:12 +00:00
|
|
|
def random(self) -> Self:
|
2024-04-22 07:12:59 +00:00
|
|
|
"""
|
2024-04-26 19:48:30 +00:00
|
|
|
Overrides the parent's random, since a random line
|
|
|
|
also needs to have a offset vector.
|
2024-04-22 07:12:59 +00:00
|
|
|
|
2024-04-26 19:48:30 +00:00
|
|
|
This is an example of the **Open/Closed Principle**.
|
2024-04-22 07:12:59 +00:00
|
|
|
We aren't modifying the parent classes' random function
|
|
|
|
since doing so would be fragile and break if the
|
|
|
|
parent class added more options.
|
|
|
|
|
|
|
|
Instead we just call it, and extend it with additional
|
|
|
|
randomization.
|
|
|
|
"""
|
|
|
|
super().random()
|
|
|
|
magnitude = random.random() * 100
|
|
|
|
degrees = random.random() * 360
|
|
|
|
return self.vec(degrees, magnitude)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def end_vec(self):
|
|
|
|
"""
|
2024-04-26 19:48:30 +00:00
|
|
|
Line goes from world_x, world_y to this position which
|
|
|
|
results from adding (world_x, world_y) + (offset_x, offset_y).
|
2024-04-22 07:12:59 +00:00
|
|
|
"""
|
|
|
|
return (
|
2024-04-23 03:54:18 +00:00
|
|
|
self.world_x + self._offset_vec[0],
|
|
|
|
self.world_y + self._offset_vec[1],
|
2024-04-22 07:12:59 +00:00
|
|
|
)
|