improve examples
This commit is contained in:
parent
59bad0c25e
commit
3e4a900753
@ -0,0 +1,6 @@
|
||||
from .doodles import Doodle, Group
|
||||
from .lines import Line
|
||||
from .shapes import Circle, Rectangle
|
||||
from .color import Color
|
||||
|
||||
__all__ = [Doodle, Group, Line, Circle, Rectangle, Color]
|
@ -1,7 +1,5 @@
|
||||
import random
|
||||
import copy
|
||||
import math
|
||||
import pygame
|
||||
from abc import ABC, abstractmethod
|
||||
from .color import Color
|
||||
from .world import world
|
||||
@ -171,94 +169,6 @@ class Doodle(ABC):
|
||||
return self.x, self.y
|
||||
|
||||
|
||||
class Line(Doodle):
|
||||
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):
|
||||
return f"Line(pos={self.pos_vec}, end={self.end_vec}, {self._color})"
|
||||
|
||||
def draw(self, screen):
|
||||
"""
|
||||
Implementation of the abstract draw function for the line.
|
||||
|
||||
Note: This is a classic violation of single responsibility.
|
||||
|
||||
Instead, you could imagine a class like:
|
||||
|
||||
class DrawingBackend:
|
||||
def draw_doodle(doodle_type, doodle): ...
|
||||
|
||||
class PygameBackend(DrawingBackend):
|
||||
def draw_line(...): ...
|
||||
|
||||
This would make it possible to attach different
|
||||
drawing backends, restoring single-responsibility
|
||||
to the class and gaining flexibility from separating
|
||||
presentation logic from data manipulation.
|
||||
"""
|
||||
pygame.draw.line(screen, self._color, self.pos_vec, self.end_vec)
|
||||
|
||||
def to(self, x: float, y: float) -> "Doodle":
|
||||
"""
|
||||
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
|
||||
|
||||
def vec(self, degrees: float, magnitude: float):
|
||||
"""
|
||||
Alternate constructor, to create offset vector from angle & length.
|
||||
"""
|
||||
return self.to(
|
||||
magnitude * math.cos(math.radians(degrees)),
|
||||
magnitude * math.sin(math.radians(degrees)),
|
||||
)
|
||||
|
||||
def random(self) -> "Doodle":
|
||||
"""
|
||||
Overrides the parent's random, by extending the behavior.
|
||||
|
||||
This is an example of the open/closed principle.
|
||||
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):
|
||||
"""
|
||||
Parallel to pos_vec for end of line.
|
||||
"""
|
||||
return (
|
||||
self.x + self._offset_vec[0],
|
||||
self.y + self._offset_vec[1],
|
||||
)
|
||||
|
||||
|
||||
class Group(Doodle):
|
||||
"""
|
||||
For now, only Group objects can have child doodles.
|
||||
@ -345,85 +255,3 @@ class Group(Doodle):
|
||||
# if we understand the implications of tightly
|
||||
# binding the implementations of these two classes.
|
||||
return self
|
||||
|
||||
|
||||
class Circle(Doodle):
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
This is a less interesting class than Line, but very similar.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
# circle is a position & radius
|
||||
self._radius = 0
|
||||
|
||||
def __repr__(self):
|
||||
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)
|
||||
|
||||
def radius(self, r: float) -> "Doodle":
|
||||
"""
|
||||
A setter for the circle's radius.
|
||||
"""
|
||||
self._radius = r
|
||||
return self
|
||||
|
||||
def grow(self, by: float):
|
||||
"""
|
||||
Modify radius by an amount. (Negative to shrink.)
|
||||
"""
|
||||
return self.radius(self._radius + by)
|
||||
|
||||
def random(self) -> "Doodle":
|
||||
super().random()
|
||||
# constrain to 10-100
|
||||
return self.radius(random.random()*90 + 10)
|
||||
|
||||
|
||||
class Rectangle(Doodle):
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
For compatibility with circle, the rectangle is centered at pos
|
||||
and expands out width/2, height/2 in each cardinal direction.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self._width = 100
|
||||
self._height = 100
|
||||
|
||||
def __repr__(self):
|
||||
return f"Rect(pos={self.pos_vec}, width={self._width}, height={self._height}, parent={self._parent})"
|
||||
|
||||
def draw(self, screen):
|
||||
rect = pygame.Rect(
|
||||
self.x - self._width/2,
|
||||
self.y - self._height/2,
|
||||
self._width,
|
||||
self._height,
|
||||
)
|
||||
pygame.draw.rect(screen, self._color, rect)
|
||||
|
||||
def width(self, w: float) -> "Doodle":
|
||||
"""
|
||||
A setter for the width
|
||||
"""
|
||||
self._width = w
|
||||
return self
|
||||
|
||||
def height(self, h: float) -> "Doodle":
|
||||
"""
|
||||
A setter for the height
|
||||
"""
|
||||
self._height = h
|
||||
return self
|
||||
|
||||
def grow(self, dw: float, dh: float):
|
||||
"""
|
||||
Modify radius by an amount. (Negative to shrink.)
|
||||
"""
|
||||
return self.width(self._w + dw).height(self._h + dh)
|
||||
|
||||
def random(self, upper=100) -> "Doodle":
|
||||
super().random()
|
||||
# constrain to 10-100
|
||||
return self.width(random.random()*upper + 10).height(random.random()*upper + 10)
|
||||
|
@ -1,4 +1,4 @@
|
||||
from doodles.doodles import Group, Circle, Color
|
||||
from doodles import Circle, Color
|
||||
import random
|
||||
from doodles.world import world
|
||||
|
||||
@ -17,7 +17,7 @@ Objects without an update method are static.
|
||||
class Ball(Circle):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.speed = 0.005 + random.random() * 0.005
|
||||
self.speed = 9 + random.random() * 5
|
||||
|
||||
def update(self):
|
||||
self.move(0, self.speed)
|
||||
@ -29,8 +29,8 @@ class Ball(Circle):
|
||||
class GravityBall(Circle):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.accel = 0.0000001 # accel per frame
|
||||
self.speed = random.random() * 0.002
|
||||
self.accel = 0.5 # accel per frame
|
||||
self.speed = random.random() * 10
|
||||
|
||||
def update(self):
|
||||
self.speed += self.accel
|
||||
@ -40,5 +40,5 @@ class GravityBall(Circle):
|
||||
self.pos(self.x, world.HEIGHT - 10.01)
|
||||
|
||||
def create():
|
||||
balls = [Ball().pos(40*i, 0).radius(10).color(Color.BLUE) for i in range(21)]
|
||||
grav = [GravityBall().pos(20+40*i, 0).radius(10).color(Color.PURPLE) for i in range(21)]
|
||||
[Ball().pos(40*i, 0).radius(10).color(Color.BLUE) for i in range(21)]
|
||||
[GravityBall().pos(20+40*i, 0).radius(10).color(Color.PURPLE) for i in range(21)]
|
||||
|
@ -1,9 +1,15 @@
|
||||
from doodles.doodles import Group, Circle, Color
|
||||
from doodles.world import world
|
||||
from doodles import Group, Circle, Color
|
||||
|
||||
def color_cycle():
|
||||
while True:
|
||||
yield Color.RED
|
||||
yield Color.ORANGE
|
||||
yield Color.YELLOW
|
||||
|
||||
def create():
|
||||
color = color_cycle()
|
||||
g = Group().pos(400, 300)
|
||||
for r in range(20, 50, 5):
|
||||
Circle(g).radius(r).color(Color.random()).z_index(-r)
|
||||
for r in range(60, 150, 10):
|
||||
Circle(g).radius(r).color(Color.random()).z_index(-r)
|
||||
for r in range(20, 100, 12):
|
||||
Circle(g).radius(r).color(next(color)).z_index(-r)
|
||||
for r in range(100, 250, 12):
|
||||
Circle(g).radius(r).color(next(color)).z_index(-r)
|
||||
|
@ -1,4 +1,4 @@
|
||||
from doodles.doodles import Group, Line, Circle, Color
|
||||
from doodles import Group, Circle, Color
|
||||
|
||||
def original():
|
||||
g = Group()
|
||||
|
@ -1,14 +1,16 @@
|
||||
from doodles.doodles import Group, Line
|
||||
from doodles import Group, Line
|
||||
from doodles.layouts import make_grid
|
||||
import random
|
||||
|
||||
def same_spiral():
|
||||
def spirals():
|
||||
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)
|
||||
if random.random() > 0.1:
|
||||
Line(g).vec(d, 200 - d)
|
||||
yield g
|
||||
|
||||
def create():
|
||||
# Make copies, moving each one and modifying the color
|
||||
make_grid(same_spiral(), 3, 4, 250, 140, x_offset=70, y_offset=20)
|
||||
make_grid(spirals(), 3, 4, 250, 140, x_offset=70, y_offset=20)
|
||||
|
@ -1,5 +1,5 @@
|
||||
from doodles.doodles import Group, Rectangle, Color
|
||||
from doodles import Rectangle
|
||||
|
||||
def create():
|
||||
for _ in range(100):
|
||||
r = Rectangle().random(250)
|
||||
Rectangle().random(250)
|
||||
|
92
src/doodles/lines.py
Normal file
92
src/doodles/lines.py
Normal file
@ -0,0 +1,92 @@
|
||||
import math
|
||||
import random
|
||||
import pygame
|
||||
from .doodles import Doodle
|
||||
|
||||
class Line(Doodle):
|
||||
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):
|
||||
return f"Line(pos={self.pos_vec}, end={self.end_vec}, {self._color})"
|
||||
|
||||
def draw(self, screen):
|
||||
"""
|
||||
Implementation of the abstract draw function for the line.
|
||||
|
||||
Note: This is a classic violation of single responsibility.
|
||||
|
||||
Instead, you could imagine a class like:
|
||||
|
||||
class DrawingBackend:
|
||||
def draw_doodle(doodle_type, doodle): ...
|
||||
|
||||
class PygameBackend(DrawingBackend):
|
||||
def draw_line(...): ...
|
||||
|
||||
This would make it possible to attach different
|
||||
drawing backends, restoring single-responsibility
|
||||
to the class and gaining flexibility from separating
|
||||
presentation logic from data manipulation.
|
||||
"""
|
||||
pygame.draw.aaline(screen, self._color, self.pos_vec, self.end_vec)
|
||||
|
||||
def to(self, x: float, y: float) -> "Doodle":
|
||||
"""
|
||||
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
|
||||
|
||||
def vec(self, degrees: float, magnitude: float):
|
||||
"""
|
||||
Alternate constructor, to create offset vector from angle & length.
|
||||
"""
|
||||
return self.to(
|
||||
magnitude * math.cos(math.radians(degrees)),
|
||||
magnitude * math.sin(math.radians(degrees)),
|
||||
)
|
||||
|
||||
def random(self) -> "Doodle":
|
||||
"""
|
||||
Overrides the parent's random, by extending the behavior.
|
||||
|
||||
This is an example of the open/closed principle.
|
||||
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):
|
||||
"""
|
||||
Parallel to pos_vec for end of line.
|
||||
"""
|
||||
return (
|
||||
self.x + self._offset_vec[0],
|
||||
self.y + self._offset_vec[1],
|
||||
)
|
||||
|
@ -44,7 +44,8 @@ def main(modname: str = None):
|
||||
else:
|
||||
load_module(examples[ex_index])
|
||||
|
||||
elapsed = last_update = 0
|
||||
elapsed = 0
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
while True:
|
||||
for event in pygame.event.get():
|
||||
@ -58,11 +59,12 @@ def main(modname: str = None):
|
||||
elif event.key == pygame.K_LEFT:
|
||||
ex_index = (ex_index - 1) % len(examples)
|
||||
load_module(examples[ex_index])
|
||||
elapsed = pygame.time.get_ticks() - last_update
|
||||
elapsed += clock.tick(FPS)
|
||||
while elapsed > MS_PER_FRAME:
|
||||
elapsed -= MS_PER_FRAME
|
||||
world.tick()
|
||||
world.render()
|
||||
#print(clock.get_fps())
|
||||
pygame.display.flip()
|
||||
|
||||
|
||||
|
87
src/doodles/shapes.py
Normal file
87
src/doodles/shapes.py
Normal file
@ -0,0 +1,87 @@
|
||||
import random
|
||||
import pygame
|
||||
from .doodles import Doodle
|
||||
|
||||
|
||||
class Circle(Doodle):
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
This is a less interesting class than Line, but very similar.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
# circle is a position & radius
|
||||
self._radius = 0
|
||||
|
||||
def __repr__(self):
|
||||
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)
|
||||
|
||||
def radius(self, r: float) -> "Doodle":
|
||||
"""
|
||||
A setter for the circle's radius.
|
||||
"""
|
||||
self._radius = r
|
||||
return self
|
||||
|
||||
def grow(self, by: float):
|
||||
"""
|
||||
Modify radius by an amount. (Negative to shrink.)
|
||||
"""
|
||||
return self.radius(self._radius + by)
|
||||
|
||||
def random(self) -> "Doodle":
|
||||
super().random()
|
||||
# constrain to 10-100
|
||||
return self.radius(random.random() * 90 + 10)
|
||||
|
||||
|
||||
class Rectangle(Doodle):
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
For compatibility with circle, the rectangle is centered at pos
|
||||
and expands out width/2, height/2 in each cardinal direction.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self._width = 100
|
||||
self._height = 100
|
||||
|
||||
def __repr__(self):
|
||||
return f"Rect(pos={self.pos_vec}, width={self._width}, height={self._height}, parent={self._parent})"
|
||||
|
||||
def draw(self, screen):
|
||||
rect = pygame.Rect(
|
||||
self.x - self._width / 2,
|
||||
self.y - self._height / 2,
|
||||
self._width,
|
||||
self._height,
|
||||
)
|
||||
pygame.draw.rect(screen, self._color, rect)
|
||||
|
||||
def width(self, w: float) -> "Doodle":
|
||||
"""
|
||||
A setter for the width
|
||||
"""
|
||||
self._width = w
|
||||
return self
|
||||
|
||||
def height(self, h: float) -> "Doodle":
|
||||
"""
|
||||
A setter for the height
|
||||
"""
|
||||
self._height = h
|
||||
return self
|
||||
|
||||
def grow(self, dw: float, dh: float):
|
||||
"""
|
||||
Modify radius by an amount. (Negative to shrink.)
|
||||
"""
|
||||
return self.width(self._w + dw).height(self._h + dh)
|
||||
|
||||
def random(self, upper=100) -> "Doodle":
|
||||
super().random()
|
||||
# constrain to 10-100
|
||||
return self.width(random.random() * upper + 10).height(
|
||||
random.random() * upper + 10
|
||||
)
|
Loading…
Reference in New Issue
Block a user