def run(self, rocket=None): """ Execute the Planet (and Rocket) simulation. Optional parameters: * **rocket** Reference to a Rocket object - sets the initial view """ if rocket: viewalt = rocket.altitude viewanom = rocket.tanomaly else: viewalt = 0 viewanom = pi / 2 self.viewaltitude = self.kwargs.get('viewalt', viewalt) # how high to look self.viewanomaly = self.kwargs.get('viewanom', viewanom) # where to look self.viewanomalyd = self.kwargs.get('viewanomd', degrees(self.viewanomaly)) self._planetcircle = Circle((0, 0), self.radius, style=LineStyle(1, Color(self.color, 1)), color=Color(self.color, 0.5)) r = self.radius + self.viewaltitude self.viewPosition = (r * cos(self.viewanomaly), r * sin(self.viewanomaly)) super().run()
def __init__(self, **kwargs): scale = kwargs.get("viewscale", 10) # 10 pixels per meter default self.radius = kwargs.get("radius", 6.371e6) # Earth - meters self.mass = kwargs.get("planetmass", 5.9722e24) # Earth - kg self.color = kwargs.get("color", 0x008040) # greenish self.viewaltitude = kwargs.get("viewalt", 0) # how high to look self.viewanomaly = kwargs.get("viewanom", pi / 2) # where to look self.viewanomalyd = kwargs.get("viewanomd", degrees(self.viewanomaly)) self._planetcircle = Circle( (0, 0), self.radius, style=LineStyle(1, Color(self.color, 1)), color=Color(self.color, 0.5), ) super().__init__(scale)
class Point(_Point): """ Basic point object representing any point in a geometrical sense. An instantiated Point object is *callable* and will return a tuple with its logical position as an (x,y) pair. :param tuple(float,float) pos: Position in physical or logical units. :param \\**kwargs: See below :Optional Keyword Arguments: * **positioning** (*str*) One of 'logical' (default) or 'physical' * **size** (*int*) Radius of the point (in pixels) * **color** (*Color*) Valid :class:`~ggame.asset.Color` object * **style** (*LineStyle*) Valid :class:`~ggame.asset.LineStyle` object Example: .. literalinclude:: ../examples/pointpoint.py """ _defaultsize = 5 _defaultstyle = LineStyle(0, Color(0, 1)) def __init__(self, *args, **kwargs): super().__init__( CircleAsset(self._defaultsize, self._defaultstyle, self._defaultcolor), *args, **kwargs) def _buildAsset(self): return CircleAsset(self._stdinputs.size(), self._stdinputs.style(), self._stdinputs.color())
class Point(_Point): """ Basic point object representing any point in a geometrical sense. An instantiated Point object is *callable* and will return a tuple with its logical position as an (x,y) pair. :param tuple(float,float) pos: Position in physical or logical units. :param \**kwargs: See below :Optional Keyword Arguments: * **positioning** (*str*) One of 'logical' (default) or 'physical' * **size** (*int*) Radius of the point (in pixels) * **color** (*Color*) Valid :class:`~ggame.asset.Color` object * **style** (*LineStyle*) Valid :class:`~ggame.asset.LineStyle` object Example:: from ggame.asset import Color from ggame.point import Point from ggame.mathapp import MathApp p1 = Point((0,1), color=Color(0xff8000, 1.0)) p1.movable = True # An orange point that can be moved p2 = Point(lambda: (p1()[0], p1()[1]+1)) # A point position based on P1 p3 = Point((1,0)) # A third, fixed point MathApp().run() """ _defaultsize = 5 _defaultstyle = LineStyle(0, Color(0, 1)) def __init__(self, *args, **kwargs): super().__init__(CircleAsset(self._defaultsize, self._defaultstyle, self._defaultcolor), *args, **kwargs) def _buildAsset(self): return CircleAsset(self._stdinputs.size(), self._stdinputs.style(), self._stdinputs.color())
class Circle(_MathVisual): """ Create a circle on the screen. This is a subclass of :class:`~ggame.sprite.Sprite` and :class:`~ggame.mathapp._MathVisual` but most of the inherited members are of little use and are not shown in the documentation. :param \*args: See below :param \**kwargs: See below :Required Arguments: * **pos** (*tuple(float,float)*) Center point of the circle, which may be a literal tuple of floats, or a reference to any object or function that returns or evaluates to a tuple of floats. * **radius** [float or Point] Radius of the circle (logical units) or a :class:`~ggame.point.Point` on the circle. :Optional Keyword Arguments: * **positioning** (*str*) One of 'logical' or 'physical' * **style** (*LineStyle*) Valid :class:`~ggame.asset.LineStyle` object * **color** (*Color*) Valid :class:`~ggame.color.Color` object` Example:: from ggame.point import Point from ggame.circle import Circle from ggame.mathapp import MathApp p1 = Point((2,1)) c = Circle(p1, 1.4) MathApp().run() """ _posinputsdef = ['pos'] _nonposinputsdef = ['radius'] _defaultcolor = Color(0, 0) def __init__(self, *args, **kwargs): super().__init__( CircleAsset(0, self._defaultstyle, self._defaultcolor), *args, **kwargs) self._touchAsset() self.fxcenter = self.fycenter = 0.5 def _buildAsset(self): pcenter = self._spposinputs.pos try: pradius = MathApp.distance( self._posinputs.pos(), self._nposinputs.radius()) * MathApp._scale except (AttributeError, TypeError): pradius = self._nposinputs.radius() * MathApp._scale style = self._stdinputs.style() fill = self._stdinputs.color() ymax = pcenter[1] + pradius ymin = pcenter[1] - pradius xmax = pcenter[0] + pradius xmin = pcenter[0] - pradius try: if ymin > MathApp.height or ymax < 0 or xmax < 0 or xmin > MathApp.width: return CircleAsset(pradius, style, fill) elif pradius > 2 * MathApp.width: # here begins unpleasant hack to overcome crappy circles poly = self._buildPolygon(pcenter, pradius) if len(poly): passet = PolygonAsset(poly, style, fill) return passet except AttributeError: return CircleAsset(pradius, style, fill) return CircleAsset(pradius, style, fill) def _buildPolygon(self, pcenter, pradius): """ pcenter is in screen relative coordinates. returns a coordinate list in circle relative coordinates """ xcepts = [ self._findIntercepts(pcenter, pradius, 0, 0, 0, MathApp.height), self._findIntercepts(pcenter, pradius, 0, 0, MathApp.width, 0), self._findIntercepts(pcenter, pradius, MathApp.width, 0, MathApp.width, MathApp.height), self._findIntercepts(pcenter, pradius, 0, MathApp.height, MathApp.width, MathApp.height) ] ilist = [] for x in xcepts: if x and len(x) < 2: ilist.extend(x) #ilist is a list of boundary intercepts that are screen-relative if len(ilist) > 1: xrange = ilist[-1][0] - ilist[0][0] yrange = ilist[-1][1] - ilist[0][1] numpoints = 20 inx = 0 for i in range(numpoints): icepts = self._findIntercepts( pcenter, pradius, pcenter[0], pcenter[1], ilist[0][0] + xrange * (i + 1) / (numpoints + 1), ilist[0][1] + yrange * (i + 1) / (numpoints + 1)) if len(icepts): ilist.insert(inx + 1, icepts[0]) inx = inx + 1 self._addBoundaryVertices(ilist, pcenter, pradius) ilist.append(ilist[0]) ilist = [(i[0] - pcenter[0], i[1] - pcenter[1]) for i in ilist] return ilist def _addBoundaryVertices(self, plist, pcenter, pradius): """ Sides 0=top, 1=right, 2=bottom, 3=left """ #figure out rotation in point sequence cw = 0 try: rtst = plist[0:3] + [plist[0]] for p in range(3): cw = cw + (rtst[p + 1][0] - rtst[p][0]) * (rtst[p + 1][1] + rtst[p][1]) except IndexError: #print(plist) return cw = self._sgn(cw) cw = 1 if cw < 0 else 0 vertices = ((-100, -100), (MathApp.width + 100, -100), (MathApp.width + 100, MathApp.height + 100), (-100, MathApp.height + 100)) nextvertex = [(vertices[0], vertices[1]), (vertices[1], vertices[2]), (vertices[2], vertices[3]), (vertices[3], vertices[0])] nextsides = [(3, 1), (0, 2), (1, 3), (2, 0)] edges = ((None, 0), (MathApp.width, None), (None, MathApp.height), (0, None)) endside = startside = None for side in range(4): if endside is None and (edges[side][0] == round(plist[-1][0]) or edges[side][1] == round(plist[-1][1])): endside = side if startside is None and (edges[side][0] == round(plist[0][0]) or edges[side][1] == round(plist[0][1])): startside = side iterations = 0 while startside != endside: iterations = iterations + 1 if iterations > 20: break if endside != None and startside != None: # and endside != startside plist.append(nextvertex[endside][cw]) endside = nextsides[endside][cw] def _sgn(self, x): return 1 if x >= 0 else -1 def _findIntercepts(self, c, r, x1, y1, x2, y2): """ c (center) and x and y values are physical, screen relative. function returns coordinates in screen relative format """ x1n = x1 - c[0] x2n = x2 - c[0] y1n = y1 - c[1] y2n = y2 - c[1] dx = x2n - x1n dy = y2n - y1n dr = sqrt(dx * dx + dy * dy) D = x1n * y2n - x2n * y1n disc = r * r * dr * dr - D * D dr2 = dr * dr if disc <= 0: # less than two solutions return [] sdisc = sqrt(disc) x = [(D * dy + self._sgn(dy) * dx * sdisc) / dr2 + c[0], (D * dy - self._sgn(dy) * dx * sdisc) / dr2 + c[0]] y = [(-D * dx + abs(dy) * sdisc) / dr2 + c[1], (-D * dx - abs(dy) * sdisc) / dr2 + c[1]] getcoords = lambda x, y, c: [ (x, y) ] if x >= 0 and x <= MathApp.width and y >= 0 and y <= MathApp.height else [ ] res = getcoords(x[0], y[0], c) res.extend(getcoords(x[1], y[1], c)) return res @property def center(self): return self._center() @center.setter def center(self, val): """ An ordered pair (x,y) or :class:`~ggame.point.Point` that represents the (logical) circle center. This attribute is set-able and get-able. """ newval = self.Eval(val) if newval != self._center: self._center = newval self._touchAsset() @property def radius(self): return self._radius() @radius.setter def radius(self, val): newval = self.Eval(val) """ A **float** that represents the radius of the circle. This attribugte is set-able and get-able. """ if newval != self._radius: self._radius = newval self._touchAsset() def step(self): self._touchAsset() def physicalPointTouching(self, ppos): r = MathApp.distance(self._pcenter, ppos) inner = self._pradius - self.style.width / 2 outer = self._pradius + self.style.width / 2 return r <= outer and r >= inner def translate(self, pdisp): pass
def _buildAsset(self): self._setThumb() return RectangleAsset(self._stdinputs.width(), self._stdinputs.size(), line=self._stdinputs.style(), fill=Color(0, 0))
""" Example of using the Label class. """ from ggame.asset import Color from ggame.label import Label from ggame.mathapp import MathApp L = Label( (20, 80), # physical location on screen "Initial Speed (m/s)", # text to display size=15, # text size (pixels) positioning="physical", # use physical coordinates color=Color(0x202000, 1.0), ) # text color MathApp().run()
class _MathVisual(Sprite, _MathDynamic, metaclass=ABCMeta): """ Abstract Base Class for all visual, potentially dynamic objects. :param Asset asset: A valid ggame asset object. :param list args: A list of required positional or non-positional arguments as named in the _posinputsdef and _nonposinputsdef lists overridden by child classes. :param \**kwargs: See below :Optional Keyword Arguments: * **positioning** (*string*) One of 'logical' or 'physical' * **size** (*int*) Size of the object (in pixels) * **width** (*int*) Width of the object (in pixels) * **color** (*Color*) Valid :class:`~ggame.asset.Color` object * **style** (*LineStyle*) Valid :class:`~ggame.asset.LineStyle` object """ _posinputsdef = [ ] # a list of names (string) of required positional inputs _nonposinputsdef = [ ] # a list of names (string) of required non positional inputs _defaultsize = 15 _defaultwidth = 200 _defaultcolor = Color(0, 1) _defaultstyle = LineStyle(1, Color(0, 1)) def __init__(self, asset, *args, **kwargs): MathApp._addVisual(self) #Sprite.__init__(self, asset, args[0]) _MathDynamic.__init__(self) self._movable = False self._selectable = False self._strokable = False self.selected = False """ True if object is currently selected by the UI. """ self.mouseisdown = False """ True if object is tracking UI mouse button as down. """ self._positioning = kwargs.get('positioning', 'logical') # positional inputs self._PI = namedtuple('PI', self._posinputsdef) # nonpositional inputs self._NPI = namedtuple('NPI', self._nonposinputsdef) # standard inputs (not positional) standardargs = ['size', 'width', 'color', 'style'] self._SI = namedtuple('SI', standardargs) # correct number of args? if len(args) != len(self._posinputsdef) + len(self._nonposinputsdef): raise TypeError("Incorrect number of parameters provided") self._args = args # generated named tuple of functions from positional inputs self._posinputs = self._PI(*[self.Eval(p) for p in args][:len(self._posinputsdef)]) self._getPhysicalInputs() # first positional argument must be a sprite position! Sprite.__init__(self, asset, self._pposinputs[0]) # generated named tuple of functions from nonpositional inputs if len(self._nonposinputsdef) > 0: self._nposinputs = self._NPI( *[self.Eval(p) for p in args][(-1 * len(self._nonposinputsdef)):]) else: self._nposinputs = [] self._stdinputs = self._SI( self.Eval(kwargs.get('size', self._defaultsize)), self.Eval(kwargs.get('width', self._defaultwidth)), self.Eval(kwargs.get('color', self._defaultcolor)), self.Eval(kwargs.get('style', self._defaultstyle))) self._sposinputs = self._PI(*[0] * len(self._posinputs)) self._spposinputs = self._PI(*self._pposinputs) self._snposinputs = self._NPI(*[0] * len(self._nposinputs)) self._sstdinputs = self._SI(*[0] * len(self._stdinputs)) def step(self): self._touchAsset() def _saveInputs(self, inputs): self._sposinputs, self._spposinputs, self._snposinputs, self._sstdinputs = inputs def _getInputs(self): self._getPhysicalInputs() return (self._PI(*[p() for p in self._posinputs]), self._PI(*self._pposinputs), self._NPI(*[p() for p in self._nposinputs]), self._SI(*[p() for p in self._stdinputs])) def _getPhysicalInputs(self): """ Translate all positional inputs to physical """ pplist = [] if self._positioning == 'logical': for p in self._posinputs: pval = p() try: pp = MathApp.logicalToPhysical(pval) except AttributeError: pp = MathApp._scale * pval pplist.append(pp) else: # already physical pplist = [p() for p in self._posinputs] self._pposinputs = self._PI(*pplist) def _inputsChanged(self, saved): return self._spposinputs != saved[1] or self._snposinputs != saved[ 2] or self._sstdinputs != saved[3] def destroy(self): MathApp._removeVisual(self) MathApp._removeMovable(self) MathApp._removeStrokable(self) _MathDynamic.destroy(self) Sprite.destroy(self) def _updateAsset(self, asset): if type(asset) != ImageAsset: visible = self.GFX.visible if App._win != None: App._win.remove(self.GFX) self.GFX.destroy() self.asset = asset self.GFX = self.asset.GFX self.GFX.visible = visible if App._win != None: App._win.add(self.GFX) self.position = self._pposinputs.pos @property def positioning(self): """ Whether object was created with 'logical' or 'physical' positioning. """ return self._positioning @positioning.setter def positioning(self, val): pass @property def movable(self): """ Whether object can be moved. Set-able and get-able. """ return self._movable @movable.setter def movable(self, val): if not self._dynamic: self._movable = val if val: MathApp._addMovable(self) else: MathApp._removeMovable(self) @property def selectable(self): """ Whether object can be selected by the UI. Set-able and get-able. """ return self._selectable @selectable.setter def selectable(self, val): self._selectable = val if val: MathApp._addSelectable(self) else: MathApp._removeSelectable(self) @property def strokable(self): """ Whether the object supports a click-drag input from the UI mouse. Set-able and get-able. """ return self._strokable @strokable.setter def strokable(self, val): self._strokable = val if val: MathApp._addStrokable(self) else: MathApp._removeStrokable(self) def select(self): """ Place the object in a 'selected' state. :param: None :returns: None """ self.selected = True def unselect(self): """ Place the object in an 'unselected' state. :param: None :returns: None """ self.selected = False def mousedown(self): """ Inform the object of a 'mouse down' event. :param: None :returns: None """ self.mouseisdown = True def mouseup(self): """ Inform the object of a 'mouse up' event. :param: None :returns: None """ self.mouseisdown = False def processEvent(self, event): """ Inform the object of a generic ggame event. :param event: The ggame event object to receive and process. :returns: None This method is intended to be overridden. """ pass @abstractmethod def physicalPointTouching(self, ppos): """ Determine if a physical point is considered to be touching this object. :param tuple(int,int) ppos: Physical screen coordinates. :rtype: boolean :returns: True if touching, False otherwise. This method **must** be overridden. """ pass @abstractmethod def translate(self, pdisp): """ Perform necessary processing in response to being moved by the mouse/UI. :param tuple(int,int) pdisp: Translation vector (x,y) in physical screen units. :returns: None This method **must** be overridden. """ pass def stroke(self, ppos, pdisp): """ Perform necessary processing in response to click-drag action by the mouse/UI. :param tuple(int,int) ppos: Physical coordinates of stroke start. :param tuple(int,int) pdisp: Translation vector of stroke action in physical screen units. :returns: None This method is intended to be overridden. """ pass def canStroke(self, ppos): """ Can the object respond to beginning a stroke action at the given position. :param tuple(int,int) ppos: Physical coordinates of stroke start. :rtype: Boolean :returns: True if the object can respond, False otherwise. This method is intended to be overridden. """ return False def _touchAsset(self, force=False): inputs = self._getInputs() changed = self._inputsChanged(inputs) if changed: self._saveInputs(inputs) if changed or force: self._updateAsset(self._buildAsset()) @abstractmethod def _buildAsset(self): pass
""" Example of using MathApp Point class. """ from ggame.asset import Color from ggame.point import Point from ggame.mathapp import MathApp P1 = Point((0, 1), color=Color(0xFF8000, 1.0)) P1.movable = True # An orange point that can be moved P2 = Point(lambda: (P1()[0], P1()[1] + 1)) # A point position based on P1 P3 = Point((1, 0)) # A third, fixed point MathApp().run()
class _MathVisual(Sprite, _MathDynamic, metaclass=ABCMeta): posinputsdef = [] # a list of names (string) of required positional inputs nonposinputsdef = [ ] # a list of names (string) of required non positional inputs defaultsize = 15 defaultwidth = 200 defaultcolor = Color(0, 1) defaultstyle = LineStyle(1, Color(0, 1)) def __init__(self, asset, *args, **kwargs): """ Required inputs * **asset** a ggame asset * **args** the list of required positional and nonpositional arguments, as named in the posinputsdef and nonposinputsdef lists * **kwargs** all other optional keyword arguments: positioning - logical (default) or physical, size, width, color, style movable """ MathApp._addVisual(self) #Sprite.__init__(self, asset, args[0]) _MathDynamic.__init__(self) self._movable = False self._selectable = False self._strokable = False self.selected = False self.mouseisdown = False # self.positioning = kwargs.get('positioning', 'logical') # positional inputs self.PI = namedtuple('PI', self.posinputsdef) # nonpositional inputs self.NPI = namedtuple('NPI', self.nonposinputsdef) # standard inputs (not positional) standardargs = ['size', 'width', 'color', 'style'] self.SI = namedtuple('SI', standardargs) # correct number of args? if len(args) != len(self.posinputsdef) + len(self.nonposinputsdef): raise TypeError("Incorrect number of parameters provided") self.args = args # generated named tuple of functions from positional inputs self.posinputs = self.PI(*[self.Eval(p) for p in args][:len(self.posinputsdef)]) self._getPhysicalInputs() # first positional argument must be a sprite position! Sprite.__init__(self, asset, self.pposinputs[0]) # generated named tuple of functions from nonpositional inputs if len(self.nonposinputsdef) > 0: self.nposinputs = self.NPI( *[self.Eval(p) for p in args][(-1 * len(self.nonposinputsdef)):]) else: self.nposinputs = [] self.stdinputs = self.SI( self.Eval(kwargs.get('size', self.defaultsize)), self.Eval(kwargs.get('width', self.defaultwidth)), self.Eval(kwargs.get('color', self.defaultcolor)), self.Eval(kwargs.get('style', self.defaultstyle))) self.sposinputs = self.PI(*[0] * len(self.posinputs)) self.spposinputs = self.PI(*self.pposinputs) self.snposinputs = self.NPI(*[0] * len(self.nposinputs)) self.sstdinputs = self.SI(*[0] * len(self.stdinputs)) def step(self): self._touchAsset() def _saveInputs(self, inputs): self.sposinputs, self.spposinputs, self.snposinputs, self.sstdinputs = inputs def _getInputs(self): self._getPhysicalInputs() return (self.PI(*[p() for p in self.posinputs]), self.PI(*self.pposinputs), self.NPI(*[p() for p in self.nposinputs]), self.SI(*[p() for p in self.stdinputs])) def _getPhysicalInputs(self): """ Translate all positional inputs to physical """ pplist = [] if self.positioning == 'logical': for p in self.posinputs: pval = p() try: pp = MathApp.logicalToPhysical(pval) except AttributeError: pp = MathApp._scale * pval pplist.append(pp) else: # already physical pplist = [p() for p in self.posinputs] self.pposinputs = self.PI(*pplist) def _inputsChanged(self, saved): return self.spposinputs != saved[1] or self.snposinputs != saved[ 2] or self.sstdinputs != saved[3] def destroy(self): MathApp._removeVisual(self) MathApp._removeMovable(self) MathApp._removeStrokable(self) _MathDynamic.destroy(self) Sprite.destroy(self) def _updateAsset(self, asset): if type(asset) != ImageAsset: visible = self.GFX.visible if App._win != None: App._win.remove(self.GFX) self.GFX.destroy() self.asset = asset self.GFX = self.asset.GFX self.GFX.visible = visible if App._win != None: App._win.add(self.GFX) self.position = self.pposinputs.pos @property def movable(self): return self._movable @movable.setter def movable(self, val): if not self._dynamic: self._movable = val if val: MathApp._addMovable(self) else: MathApp._removeMovable(self) @property def selectable(self): return self._selectable @selectable.setter def selectable(self, val): self._selectable = val if val: MathApp._addSelectable(self) else: MathApp._removeSelectable(self) @property def strokable(self): return self._strokable @strokable.setter def strokable(self, val): self._strokable = val if val: MathApp._addStrokable(self) else: MathApp._removeStrokable(self) def select(self): self.selected = True def unselect(self): self.selected = False def mousedown(self): self.mouseisdown = True def mouseup(self): self.mouseisdown = False def processEvent(self, event): pass # define how your class responds to mouse clicks - returns True/False @abstractmethod def physicalPointTouching(self, ppos): pass # define how your class responds to being moved (physical units) @abstractmethod def translate(self, pdisp): pass # define how your class responds to being stroked (physical units) def stroke(self, ppos, pdisp): pass # is the mousedown in a place that will result in a stroke? def canstroke(self, ppos): return False def _touchAsset(self, force=False): inputs = self._getInputs() changed = self._inputsChanged(inputs) if changed: self._saveInputs(inputs) if changed or force: self._updateAsset(self._buildAsset()) @abstractmethod def _buildAsset(self): pass