Python OOP advice

Paul McGuire ptmcg at austin.rr.com
Wed Sep 17 08:57:46 EDT 2008


On Sep 17, 6:50 am, Simon Hibbs <simon.hi... at gmail.com> wrote:
> I'm rewriting a design application for a science fiction game. in it
> you design your own starships. Each component has a mass and cost, but
> the mass can either be fixed or it can be expressed as a percentage of
> the tonnage of the overall ship.
>
> Orriginaly I thought I'd need to have a hull object which contains
> component objects, but the component objects need access to members of
> the hull object (e.g. the hull size) so that looks messy to implement.
>

I would not put this kind of intelligence into the components.

I think the issue here is that your Ship container is not really just
a generic container of ship components, but an assembly with some
specific requirements (must have 1 and only 1 hull, must have 1 or
more engines, etc.) and structure.  I would create a class called
ShipDesign that had specific members for those components that have
special logic attached to them, and then more generic list members for
collection-ish components.

Since the hull is such a significant constraint, I would make it an
initialization argument.  I would also put some kind of property on
hull representing its "capacity" (oh I see, you have something call
hull_size).

One way to generalize the fixed-cost vs. percentage-cost components
would be to have all components implement a compute_load function that
takes the ShipDesign as an argument.  Those that are fixed-cost simply
return their fixed value, those that are percentage-cost can return
their percentage of the ShipDesign's hull.hull_size - this leaves the
door open for other variations on load, that could be based on other
properties besides the hull size.

Here's how I envision your ShipDesign class:

class ShipDesign(object):
    def __init__(self, hull):
        self.hull = hull
        self.engines = []
        self.shields = []
        self.weapons = []
        self.other = []

    def compute_consumed_capacity(self):
        load = 0
        for itemlist in (self.engines, self.shields,
                        self.weapons, self.other)):
            load += sum(item.compute_load(self)
                                for item in itemlist)
        return load

    def add_engine(self,e):
        engload = e.compute_load(self)
        if engload + self.compute_consumed_capacity() >
self.hull.hull_size:
            raise ExceededHullCapacityException()
        if len(self.engines) == MAXIMUM_ALLOWED_ENGINES:
            raise ExceededMaximumConfigurationLimitException()
        self.engines.append(e)

    def set_hull(self, hull):
        if self.compute_consumed_capacity() <= hull.hull_size:
            self.hull = hull
        else:
            raise NewHullTooSmallException()

    def is_valid(self):
        if not self.engines:
            raise InvalidDesignException("must have at least 1
engine")
        ...etc...

class GenericItem(object):
    def __init__(self, name, load):
        self.name = name
        self.load = load
crewQuarters = GenericItem("Crew Quarters", 50)
disco = GenericItem("Discotheque", 10)
...etc...

Once you have a valid ShipDesign, you can then use it to construct
multiple Ship instances.

Here is how I would work around your "only one hull at a time"
problem.  Define several classes for different kinds of hulls:

class CheapHull(Hull):
    capacity = 100
    name = "EconoHull 1000"
class MediumHull(Hull):
    capacity = 500
    name = "Mainliner X50"
class TopOTheLineHull(Hull):
    capacity = 1000
    name = "LuxeMaster 5000"

and then create ship designs with a CheapHull, a MediumHull, or a
TopOTheLineHull.  In this case, the member variable of the ShipDesign
is really a class, which you would later use to construct hull
instances as part of making Ship instances from your ShipDesign.

class Ship(object):
    def __init__(self, design):
        self.hull = design.hull()
        self.engines = design.engines[:]
        ...etc...

    This way, each Ship will have its own Hull instance, so that you
can track instance-specific properties, such as damage percentage.

If you don't want to hard-code the hull types, then you can do
something similar with instances of a generic Hull class, which you
would then use as prototypes when constructing Ship instances.  Just
be careful that you don't accidentally have all ships sharing the same
hull instance!

-- Paul




More information about the Python-list mailing list