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 random
|
||||||
import copy
|
import copy
|
||||||
import math
|
|
||||||
import pygame
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from .color import Color
|
from .color import Color
|
||||||
from .world import world
|
from .world import world
|
||||||
@ -171,94 +169,6 @@ class Doodle(ABC):
|
|||||||
return self.x, self.y
|
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):
|
class Group(Doodle):
|
||||||
"""
|
"""
|
||||||
For now, only Group objects can have child doodles.
|
For now, only Group objects can have child doodles.
|
||||||
@ -345,85 +255,3 @@ class Group(Doodle):
|
|||||||
# if we understand the implications of tightly
|
# if we understand the implications of tightly
|
||||||
# binding the implementations of these two classes.
|
# binding the implementations of these two classes.
|
||||||
return self
|
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
|
import random
|
||||||
from doodles.world import world
|
from doodles.world import world
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ Objects without an update method are static.
|
|||||||
class Ball(Circle):
|
class Ball(Circle):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.speed = 0.005 + random.random() * 0.005
|
self.speed = 9 + random.random() * 5
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self.move(0, self.speed)
|
self.move(0, self.speed)
|
||||||
@ -29,8 +29,8 @@ class Ball(Circle):
|
|||||||
class GravityBall(Circle):
|
class GravityBall(Circle):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.accel = 0.0000001 # accel per frame
|
self.accel = 0.5 # accel per frame
|
||||||
self.speed = random.random() * 0.002
|
self.speed = random.random() * 10
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self.speed += self.accel
|
self.speed += self.accel
|
||||||
@ -40,5 +40,5 @@ class GravityBall(Circle):
|
|||||||
self.pos(self.x, world.HEIGHT - 10.01)
|
self.pos(self.x, world.HEIGHT - 10.01)
|
||||||
|
|
||||||
def create():
|
def create():
|
||||||
balls = [Ball().pos(40*i, 0).radius(10).color(Color.BLUE) for i in range(21)]
|
[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)]
|
[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 import Group, Circle, Color
|
||||||
from doodles.world import world
|
|
||||||
|
def color_cycle():
|
||||||
|
while True:
|
||||||
|
yield Color.RED
|
||||||
|
yield Color.ORANGE
|
||||||
|
yield Color.YELLOW
|
||||||
|
|
||||||
def create():
|
def create():
|
||||||
|
color = color_cycle()
|
||||||
g = Group().pos(400, 300)
|
g = Group().pos(400, 300)
|
||||||
for r in range(20, 50, 5):
|
for r in range(20, 100, 12):
|
||||||
Circle(g).radius(r).color(Color.random()).z_index(-r)
|
Circle(g).radius(r).color(next(color)).z_index(-r)
|
||||||
for r in range(60, 150, 10):
|
for r in range(100, 250, 12):
|
||||||
Circle(g).radius(r).color(Color.random()).z_index(-r)
|
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():
|
def original():
|
||||||
g = Group()
|
g = Group()
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
from doodles.doodles import Group, Line
|
from doodles import Group, Line
|
||||||
from doodles.layouts import make_grid
|
from doodles.layouts import make_grid
|
||||||
|
import random
|
||||||
|
|
||||||
def same_spiral():
|
def spirals():
|
||||||
while True:
|
while True:
|
||||||
# Create a group of lines all with same origin, different angles.
|
# Create a group of lines all with same origin, different angles.
|
||||||
g = Group()
|
g = Group()
|
||||||
for d in range(0, 180, 10):
|
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
|
yield g
|
||||||
|
|
||||||
def create():
|
def create():
|
||||||
# Make copies, moving each one and modifying the color
|
# 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():
|
def create():
|
||||||
for _ in range(100):
|
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:
|
else:
|
||||||
load_module(examples[ex_index])
|
load_module(examples[ex_index])
|
||||||
|
|
||||||
elapsed = last_update = 0
|
elapsed = 0
|
||||||
|
clock = pygame.time.Clock()
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
for event in pygame.event.get():
|
for event in pygame.event.get():
|
||||||
@ -58,11 +59,12 @@ def main(modname: str = None):
|
|||||||
elif event.key == pygame.K_LEFT:
|
elif event.key == pygame.K_LEFT:
|
||||||
ex_index = (ex_index - 1) % len(examples)
|
ex_index = (ex_index - 1) % len(examples)
|
||||||
load_module(examples[ex_index])
|
load_module(examples[ex_index])
|
||||||
elapsed = pygame.time.get_ticks() - last_update
|
elapsed += clock.tick(FPS)
|
||||||
while elapsed > MS_PER_FRAME:
|
while elapsed > MS_PER_FRAME:
|
||||||
elapsed -= MS_PER_FRAME
|
elapsed -= MS_PER_FRAME
|
||||||
world.tick()
|
world.tick()
|
||||||
world.render()
|
world.render()
|
||||||
|
#print(clock.get_fps())
|
||||||
pygame.display.flip()
|
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