From aa82295182eeaf33a2435852f4103f94f7ae7345 Mon Sep 17 00:00:00 2001 From: James Turk Date: Mon, 22 Apr 2024 00:11:55 -0500 Subject: [PATCH] fix copying --- src/doodles/doodles.py | 44 +++++++++++++++++++++------------- src/doodles/examples/copies.py | 16 +++++++++++++ src/doodles/examples/grid.py | 15 +++++++----- src/doodles/layouts.py | 14 +---------- src/doodles/main.py | 2 +- 5 files changed, 55 insertions(+), 36 deletions(-) create mode 100644 src/doodles/examples/copies.py diff --git a/src/doodles/doodles.py b/src/doodles/doodles.py index d0b78ed..cd1c8d3 100644 --- a/src/doodles/doodles.py +++ b/src/doodles/doodles.py @@ -36,7 +36,11 @@ class Doodle(ABC): # All references to _pos_vec are internal to the class, # so it will be trivial to swap this out later. self._pos_vec = (0, 0) - if parent: + self._register() + + def _register(self): + """ register with parent and world """ + if self._parent: # register with parent for updates self._parent.add(self) world.add(self) @@ -63,20 +67,20 @@ class Doodle(ABC): Additionally, while a shallow copy is enough for most cases, it will be possible for child classes to override - this to opt for a deepcopy or other logic. + this. """ new = copy.copy(self) - world.add(new) + new._register() return new - def color(self, r: int, g: int, b: int) -> "Doodle": + def color(self, color: tuple[int, int, int]) -> "Doodle": """ Color works as a kind of setter function. The only unique part is that it returns self, accomodating the chained object pattern. """ - self._color = (r, g, b) + self._color = color return self def pos(self, x: float, y: float) -> "Doodle": @@ -111,11 +115,11 @@ class Doodle(ABC): """ x = random.random() * world.WIDTH y = random.random() * world.HEIGHT - r, g, b = Color.random() + color = Color.random() # again here, we opt to use the setters so that # future extensions to their behavior will be # used by all downstream functions - return self.pos(x, y).color(r, g, b) + return self.pos(x, y).color(color) @property def x(self) -> float: @@ -265,10 +269,13 @@ class Group(Doodle): in some languages would be much trickier to pull off. """ - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super().__init__(parent) self._doodles = [] + def __repr__(self): + return f"Group(pos={self.pos_vec}, doodles={len(self._doodles)})" + def draw(self, screen): """ Groups, despite being an abstract concept, are drawable. @@ -285,11 +292,16 @@ class Group(Doodle): We are storing a list, so deep copies are necessary. """ - new = copy.deepcopy(self) - world.add(new) + new = copy.copy(self) + new._register() + new._doodles = [] + for child in self._doodles: + child = copy.copy(child) + child._parent = new + child._register() return new - def color(self, r: int, g: int, b: int) -> "Doodle": + def color(self, color: tuple[int, int, int]) -> "Doodle": """ Another override. @@ -299,9 +311,9 @@ class Group(Doodle): We don't cascade pos() calls, why not? """ - super().color(r, g, b) + super().color(color) for d in self._doodles: - d.color(r, g, b) + d.color(color) return self def add(self, doodle: "Doodle") -> "Group": @@ -338,7 +350,7 @@ class Circle(Doodle): self._radius = 0 def __repr__(self): - return f"Circle(pos={self.pos_vec}, radius={self._radius}, {self._color})" + return f"Circle(pos={self.pos_vec}, radius={self._radius}, {self._color}, parent={self._parent}))" def draw(self, screen): pygame.draw.circle(screen, self._color, self.pos_vec, self._radius) @@ -359,4 +371,4 @@ class Circle(Doodle): def random(self) -> "Doodle": super().random() # constrain to 10-100 - return self.radius(random.random*90 + 10) + return self.radius(random.random()*90 + 10) diff --git a/src/doodles/examples/copies.py b/src/doodles/examples/copies.py new file mode 100644 index 0000000..b11aecd --- /dev/null +++ b/src/doodles/examples/copies.py @@ -0,0 +1,16 @@ +from doodles.doodles import Group, Line, Circle, Color + +def original(): + g = Group() + c = Circle(g).radius(80).color(Color.RED).pos(0, 0) + for _ in range(15): + c = c.copy().move(45, 45) + return g + +r = original() +r.copy().move(200, 0).color(Color.GREEN) +r.copy().move(400, 0).color(Color.BLUE) + +# from doodles.world import world +# for d in world._drawables: +# print(" >", d) diff --git a/src/doodles/examples/grid.py b/src/doodles/examples/grid.py index 2154676..f9fda6f 100644 --- a/src/doodles/examples/grid.py +++ b/src/doodles/examples/grid.py @@ -1,10 +1,13 @@ from doodles.doodles import Group, Line -from doodles.layouts import make_grid, copies +from doodles.layouts import make_grid -# Create a group of lines all with same origin, different angles. -g = Group() -for d in range(0, 180, 10): - Line(g).vec(d, 200 - d) +def same_spiral(): + while True: + # Create a group of lines all with same origin, different angles. + g = Group() + for d in range(0, 180, 10): + Line(g).vec(d, 200 - d) + yield g # Make copies, moving each one and modifying the color -make_grid(copies(g), 3, 4, 250, 140, x_offset=70, y_offset=20) +make_grid(same_spiral(), 3, 4, 250, 140, x_offset=70, y_offset=20) diff --git a/src/doodles/layouts.py b/src/doodles/layouts.py index d8fc1da..1287d95 100644 --- a/src/doodles/layouts.py +++ b/src/doodles/layouts.py @@ -5,21 +5,9 @@ def make_grid(iterable, cols, rows, width, height, *, x_offset=0, y_offset=0): Arranges the objects in iterable in a grid with the given parameters. """ try: - doodle = next(iterable) for c in range(cols): for r in range(rows): - doodle.pos(width * c + x_offset, height * r + y_offset) doodle = next(iterable) + doodle.pos(width * c + x_offset, height * r + y_offset) except StopIteration: pass - - -def copies(doodle): - """ - Lazily makes an infinite number of copies of a given doodle. - - Can be combined with things like `make_grid` that require - an iterable of doodles to repeat. - """ - while True: - yield doodle.copy() diff --git a/src/doodles/main.py b/src/doodles/main.py index fca9440..2ada071 100644 --- a/src/doodles/main.py +++ b/src/doodles/main.py @@ -29,7 +29,7 @@ def main(modname: str): pygame.quit() sys.exit() world.render() - # print(f"world contains {world._drawables}") + #print(f"world contains {len(world._drawables)}") pygame.display.flip()