... | @@ -668,3 +668,382 @@ class TestPlayer(unittest.TestCase): |
... | @@ -668,3 +668,382 @@ class TestPlayer(unittest.TestCase): |
|
# Can not test due to hardcoded System.in use in Player.next_move
|
|
# Can not test due to hardcoded System.in use in Player.next_move
|
|
pass
|
|
pass
|
|
```
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## Shapes Example
|
|
|
|
|
|
|
|
**Draft**
|
|
|
|
|
|
|
|
```python
|
|
|
|
"""
|
|
|
|
This module provides the Shape class and related constants which serve as the
|
|
|
|
base for other (specialized) shapes.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import abc
|
|
|
|
|
|
|
|
WIDTH_LABEL = 12 # Label Output Width
|
|
|
|
WIDTH_VALUE = 24 # Value Output Width
|
|
|
|
|
|
|
|
STR_FMT = f"{{:<{WIDTH_LABEL}}}:{{:>{WIDTH_VALUE}}}\n"
|
|
|
|
FPT_FMT = f"{{:<{WIDTH_LABEL}}}:{{:>{WIDTH_VALUE}.4f}}\n"
|
|
|
|
|
|
|
|
|
|
|
|
class Shape(metaclass=abc.ABCMeta):
|
|
|
|
"""
|
|
|
|
Shape in a 2-D Cartesian Plane
|
|
|
|
"""
|
|
|
|
|
|
|
|
@property
|
|
|
|
@abc.abstractmethod
|
|
|
|
def name(self) -> str:
|
|
|
|
"""
|
|
|
|
Provide read-only access to the name attribute.
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
NotImplemented Error if not overridden by subclass
|
|
|
|
"""
|
|
|
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def area(self) -> float:
|
|
|
|
"""
|
|
|
|
Compute the area
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
NotImplemented Error if not overridden by subclass
|
|
|
|
"""
|
|
|
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def perimeter(self) -> float:
|
|
|
|
"""
|
|
|
|
Compute the perimeter
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
NotImplemented Error if not overridden by subclass
|
|
|
|
"""
|
|
|
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def __deepcopy__(self, memo):
|
|
|
|
"""
|
|
|
|
Return a new duplicate Shape
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
NotImplemented Error if not overridden by subclass
|
|
|
|
"""
|
|
|
|
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def __str__(self) -> str:
|
|
|
|
"""
|
|
|
|
Print the shape
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
NotImplemented Error if not overridden by subclass
|
|
|
|
"""
|
|
|
|
|
|
|
|
return STR_FMT.format("Name", self.name)
|
|
|
|
```
|
|
|
|
|
|
|
|
```python
|
|
|
|
import copy
|
|
|
|
|
|
|
|
from shapes.shape import (Shape, FPT_FMT)
|
|
|
|
|
|
|
|
|
|
|
|
class Square(Shape):
|
|
|
|
"""
|
|
|
|
A Rectangle with 4 Equal Sides
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, side=1):
|
|
|
|
"""
|
|
|
|
Construct a Square
|
|
|
|
"""
|
|
|
|
|
|
|
|
self._side = side
|
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self) -> str:
|
|
|
|
"""
|
|
|
|
Provide read-only access to the name attribute.
|
|
|
|
"""
|
|
|
|
|
|
|
|
return "Square"
|
|
|
|
|
|
|
|
@property
|
|
|
|
def side(self):
|
|
|
|
return self._side
|
|
|
|
|
|
|
|
@side.setter
|
|
|
|
def side(self, some_value):
|
|
|
|
self._side = some_value
|
|
|
|
|
|
|
|
def area(self):
|
|
|
|
"""
|
|
|
|
Compute the area
|
|
|
|
"""
|
|
|
|
return self._side ** 2.0
|
|
|
|
|
|
|
|
def perimeter(self):
|
|
|
|
"""
|
|
|
|
Compute the perimeter
|
|
|
|
"""
|
|
|
|
|
|
|
|
return 4 * self._side
|
|
|
|
|
|
|
|
def __deepcopy__(self, memo):
|
|
|
|
"""
|
|
|
|
Return a new duplicate Shape
|
|
|
|
"""
|
|
|
|
|
|
|
|
return Square(copy.deepcopy(self.side))
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
"""
|
|
|
|
Print the Square
|
|
|
|
"""
|
|
|
|
|
|
|
|
return (super().__str__()
|
|
|
|
+ FPT_FMT.format("Side", self.side)
|
|
|
|
+ FPT_FMT.format("Perimeter", self.perimeter())
|
|
|
|
+ FPT_FMT.format("Area", self.area()))
|
|
|
|
```
|
|
|
|
|
|
|
|
```python
|
|
|
|
"""
|
|
|
|
This module provides factory utilities for creating shapes. This includes
|
|
|
|
recording which Shape types are available.
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
import copy
|
|
|
|
|
|
|
|
from shapes.circle import Circle
|
|
|
|
from shapes.square import Square
|
|
|
|
from shapes.triangle import (Triangle, RightTriangle, EquilateralTriangle)
|
|
|
|
|
|
|
|
|
|
|
|
_KNOWN_SHAPES = {
|
|
|
|
"Triangle": (
|
|
|
|
Triangle(),
|
|
|
|
lambda a, b, c: Triangle(a, b, c)
|
|
|
|
),
|
|
|
|
"Right Triangle": (
|
|
|
|
RightTriangle(),
|
|
|
|
lambda base, height: RightTriangle(base, height)
|
|
|
|
),
|
|
|
|
"Equilateral Triangle": (
|
|
|
|
EquilateralTriangle(),
|
|
|
|
lambda side: EquilateralTriangle(side)
|
|
|
|
),
|
|
|
|
"Square": (
|
|
|
|
Square(),
|
|
|
|
lambda side: Square(side)
|
|
|
|
),
|
|
|
|
"Circle": (
|
|
|
|
Circle(),
|
|
|
|
lambda radius: Circle(radius)
|
|
|
|
)
|
|
|
|
} # _Dictionary_ of known shapes
|
|
|
|
|
|
|
|
|
|
|
|
def create(name):
|
|
|
|
"""
|
|
|
|
Create a Shape
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name: the shape to be created
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
A shape with the specified name or null if no matching shape is found
|
|
|
|
"""
|
|
|
|
|
|
|
|
if name in _KNOWN_SHAPES:
|
|
|
|
return copy.deepcopy(_KNOWN_SHAPES[name][0])
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def create_from_dictionary(name, values):
|
|
|
|
"""
|
|
|
|
Create a Shape
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name: the shape to be created
|
|
|
|
|
|
|
|
values: dictionary of values corresponding to the data needed
|
|
|
|
to inialize a shape
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
A shape with the specified name or null if no matching shape is found
|
|
|
|
"""
|
|
|
|
|
|
|
|
if name in _KNOWN_SHAPES:
|
|
|
|
return _KNOWN_SHAPES[name][1](**values)
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def is_known(name):
|
|
|
|
"""
|
|
|
|
Determine whether a given shape is known
|
|
|
|
|
|
|
|
Args:
|
|
|
|
name: the shape for which to query
|
|
|
|
"""
|
|
|
|
|
|
|
|
return name in _KNOWN_SHAPES
|
|
|
|
|
|
|
|
|
|
|
|
def list_known():
|
|
|
|
"""
|
|
|
|
Print a list of known Shapes
|
|
|
|
"""
|
|
|
|
return "\n".join([f" {name:}" for name in _KNOWN_SHAPES])
|
|
|
|
|
|
|
|
|
|
|
|
def number_known():
|
|
|
|
"""
|
|
|
|
Determine the number of known Shapes
|
|
|
|
"""
|
|
|
|
|
|
|
|
return len(_KNOWN_SHAPES)
|
|
|
|
```
|
|
|
|
|
|
|
|
### Shapes Driver
|
|
|
|
|
|
|
|
```python
|
|
|
|
#! /usr/bin/env python3
|
|
|
|
|
|
|
|
# Programmer : Thomas J. Kennedy
|
|
|
|
|
|
|
|
import json
|
|
|
|
import pickle
|
|
|
|
import sys
|
|
|
|
|
|
|
|
from shapes import *
|
|
|
|
from shapes import shape_factory as ShapeFactory
|
|
|
|
|
|
|
|
PROGRAM_HEADING = ("Objects & Inheritance: 2-D Shapes",
|
|
|
|
"Thomas J. Kennedy") # Program Title
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
"""
|
|
|
|
The main function. In practice I could name this
|
|
|
|
anything. The name main was selected purely
|
|
|
|
out of familiarity.
|
|
|
|
|
|
|
|
The "if __name__" line below determines what runs
|
|
|
|
"""
|
|
|
|
|
|
|
|
if len(sys.argv) < 2:
|
|
|
|
print("No input file provided.")
|
|
|
|
print("Usage: {:} input_file".format(*sys.argv))
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
shapes_filename = sys.argv[1]
|
|
|
|
|
|
|
|
print("-" * 80)
|
|
|
|
|
|
|
|
for line in PROGRAM_HEADING:
|
|
|
|
print(f"{line:^80}")
|
|
|
|
|
|
|
|
print("-" * 80)
|
|
|
|
|
|
|
|
# Examine the ShapeFactory
|
|
|
|
print("~" * 38)
|
|
|
|
print("{:^38}".format("Available Shapes"))
|
|
|
|
print("~" * 38)
|
|
|
|
|
|
|
|
print(ShapeFactory.list_known())
|
|
|
|
print("-" * 38)
|
|
|
|
print("{:>2} shapes available.".format(ShapeFactory.number_known()))
|
|
|
|
print()
|
|
|
|
|
|
|
|
# The list needs to be intialzed outside the "with" closure
|
|
|
|
shapes = list()
|
|
|
|
|
|
|
|
with open(shapes_filename, "r") as shapes_in:
|
|
|
|
for line in shapes_in:
|
|
|
|
# Split on ";" and Strip leading/trailing whitespace
|
|
|
|
# And Unpack the list
|
|
|
|
name, values = [part.strip() for part in line.split(";")]
|
|
|
|
|
|
|
|
values = json.loads(values)
|
|
|
|
|
|
|
|
shapes.append(ShapeFactory.create_from_dictionary(name, values))
|
|
|
|
|
|
|
|
# Remove all `None` entries with a list comprehension
|
|
|
|
shapes = [s for s in shapes if s is not None]
|
|
|
|
|
|
|
|
# Print all the shapes
|
|
|
|
print("~" * 38)
|
|
|
|
print("{:^38}".format("Display All Shapes"))
|
|
|
|
print("~" * 38)
|
|
|
|
|
|
|
|
for shp in shapes:
|
|
|
|
print(shp)
|
|
|
|
|
|
|
|
out_filename = "coolPickles.dat"
|
|
|
|
|
|
|
|
with open(out_filename, "wb") as pickle_file:
|
|
|
|
# LOL Nope
|
|
|
|
# for s in shapes:
|
|
|
|
# pickle.dump(s, pickle_file)
|
|
|
|
|
|
|
|
# One line, full data structure
|
|
|
|
pickle.dump(shapes, pickle_file)
|
|
|
|
|
|
|
|
with open(out_filename, "rb") as pickle_file:
|
|
|
|
rebuilt_shapes = pickle.load(pickle_file)
|
|
|
|
|
|
|
|
# Print all the rebuilt shapes
|
|
|
|
print("~" * 38)
|
|
|
|
print("{:^38}".format("Display Re-Built Shapes"))
|
|
|
|
print("~" * 38)
|
|
|
|
|
|
|
|
for shp in rebuilt_shapes:
|
|
|
|
print(shp)
|
|
|
|
|
|
|
|
print("~" * 38)
|
|
|
|
print("{:^38}".format("Display Largest Shape (Area)"))
|
|
|
|
print("~" * 38)
|
|
|
|
|
|
|
|
largest_shape = max(rebuilt_shapes, key=lambda shape: shape.area())
|
|
|
|
print(largest_shape)
|
|
|
|
|
|
|
|
print("~" * 38)
|
|
|
|
print("{:^38}".format("Display Smallest Shape (Perimeter)"))
|
|
|
|
print("~" * 38)
|
|
|
|
|
|
|
|
smallest_shape = min(rebuilt_shapes, key=lambda shape: shape.perimeter())
|
|
|
|
print(smallest_shape)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
try:
|
|
|
|
main()
|
|
|
|
except FileNotFoundError as err:
|
|
|
|
print(err)
|
|
|
|
```
|
|
|
|
|
|
|
|
### Directory Structure
|
|
|
|
|
|
|
|
```
|
|
|
|
drwxrwxr-x htmlcov
|
|
|
|
-rw-rw-r-- inputShapes.txt
|
|
|
|
-rw-rw-r-- MANIFEST
|
|
|
|
-rw-rw-r-- runTests.sh
|
|
|
|
-rw-rw-r-- setup.py
|
|
|
|
drwxrwxr-x shapes
|
|
|
|
drwxrwxr-x tests
|
|
|
|
-rw-rw-r-- tox.ini
|
|
|
|
``` |