On Sep 17, 6:50 am, Simon Hibbs <[EMAIL PROTECTED]> 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 -- http://mail.python.org/mailman/listinfo/python-list