def _distToWall(pos, angle, sWidth, w, h): "Calculate the distance to the sketch walls in the specified direction" walls = Polygon([(0,0), (w,0), (w,h), (0,h)]) w += h pts = [] for n in (-1, 0, 1): v = vec2d(w, angle + n * sWidth) line = Line(pos, vector=v) pts.extend(walls.intersect(line)) return min(dist(pos, pt) for pt in pts) if len(pts) else None
def _distToWall(pos, angle, sWidth, w, h): "Calculate the distance to the sketch walls in the specified direction" walls = Polygon([(0, 0), (w, 0), (w, h), (0, h)]) w += h pts = [] for n in (-1, 0, 1): v = vec2d(w, angle + n * sWidth) line = Line(pos, vector=v) pts.extend(walls.intersect(line)) return min(dist(pos, pt) for pt in pts) if len(pts) else None
def frameStep(self): "Update robot sprite each frame" # Target wheel speed... v = self.maxSpeed v1, v2 = self._motors v1 *= v v2 *= v # Angular speed and turning radius... w = (v1 - v2) / (2 * self.radius) self.spin = w / DEG if w: R = (v1 + v2) / (2 * w) v = w * R else: v = v1 # Acceleration... v = vec2d(v, self.angle * DEG) self.accel = times(sub(v, self.velocity), 0.05) # Update... super().frameStep() # Draw penColor on background... if self.penColor: # and self._penPosn: srf = self.sketch.scaledBgImage.surface x, y = self.posn xy = (round(x), round(y)) if self.centerPen else self._penPosn circle(srf, self.penColor, xy, self.penRadius) # Check sensors... stall = not self.motorsOff and (self.edgeAdjust or self.collideUpdate) if stall: if self._stallTime is None: self._stallTime = self.sketch.time if stall else None else: self._stallTime = None self.downColor = self._downColor() self._closest_obstacle() if self._starting: self._starting = False # Adjust wheel speed... self.costumeTime = 0 if self.motorsOff else round( 36 / (1 + 5 * self.averagePower)) # Log destruction of robot... if self._thread is not None and self._thread.log and self in self.sketch.sprites._toRemove: print("{} has been destroyed... there goes a billion dollars!". format(self._thread))
def _checkDown(self): "Update the down color sensor" x, y = self.rect.center r = 0.8 * self.radius dx, dy = vec2d(r, self.angle) r = max(1, round(0.25 * r)) d = 2 * r sk = self.sketch r = pygame.Rect(x + dx - r, y + dy - r, d, d).clip(sk.rect) if r.width: if isinstance(sk.bg, Image): c = pygame.transform.average_color(sk.bg.image.subsurface(r)) else: c = sk.bg else: c = 0, 0, 0 self.sensorDown = noise(c, self.sensorNoise, 255)
def ondraw(self): "Update robot sprite each frame" if not self.active: raise InactiveError() # Target wheel speed... sk = self.sketch v = self.maxSpeed * sk.width v1, v2 = self._motors v1 *= v v2 *= v # Angular speed and turning radius... w = (v1 - v2) / (2 * self.radius) self.spin = w / DEG if w: R = (v1 + v2) / (2 * w) v = w * R else: v = v1 # Acceleration... v = vec2d(v, self.angle) a = delta(v, self.vel) if hypot(*a) > 0.05: self.acc = delta(a, mag=0.05) else: self.vel = v self.acc = 0, 0 # Adjust wheel speed... p = self.power self.costumeTime = 0 if p == 0 else round(36 / (1 + 5 * p)) # Update position and angle... super().ondraw() # Update sensors if requested... if self._updateSensors: try: self._checkDown() self._checkFront() self._drawLEDs() self._updateSensors = False except: logError() self._startup = False
def __init__(self, ship): "Fire a new missile from the ship" super().__init__(self.original) # Initial position of missile u = vec2d(1.3 * ship.radius, ship.angle) pos = ship.pos[0] + u[0], ship.pos[1] + u[1] # Initial velocity of missile u = delta(u, mag=ship.sketch.height / 135) vel = ship.vel[0] + u[0], ship.vel[1] + u[1] self.config(width=ship.width / 4, pos=pos, vel=vel, spin=ship.spin, angle=ship.angle)
def _checkFront(self): "Update the front color sensor" # Sensor info sw = self.sensorWidth res = 0.5 * self.sensorResolution pos = delta(self.pos, vec2d(-self.radius, self.angle)) # Distance from sensor to edge of sketch obj = prox = None sk = self.sketch if sk.weight: prox = _distToWall(pos, self.angle, self.sensorWidth, *sk.size) if prox: obj = sk # Find closest object within "sensor cone" for gr in self.sensorObjects(sk): if gr is not self and gr.avgColor and hasattr(gr, "rect"): r = gr.radius view = subtend(pos, gr.rect.center, r, None if prox is None else prox + r) if view: # Object may be closer than the current closest object sep, direct, half = view if not res or half > res: # Object size meets sensor resolution threshold beta = abs(angleDifference(self.angle, direct)) - sw if beta < half or sep < r: # Object is in sensor cone pr = sep - r if beta > 0: # CLOSEST point is NOT in sensor cone dr = r + sep * (cos(half * DEG) - 1) pr += dr * (beta / half)**2 if prox is None or pr < prox: # Object is closest (so far) prox = pr obj = gr # Save data self.closestObject = obj c = rgba(sk.border if obj is sk else obj.avgColor if obj else (0, 0, 0)) self.sensorFront = noise(divAlpha(c), self.sensorNoise, 255) self.proximity = None if prox is None else max(0, round(prox))
def scroll(self, pix=None, rel=True): # Calculate scroll when not specified a = self.angle if pix is None: pix = self.focussed and a in (0, 90) if pix is False: pix, rel = 0, False elif pix is True: pix, rel = self._scrollCalc(a) # Set scrolling attributes if pix or not rel: if rel: self._scrollX += pix else: tmp = pix pix -= self._scrollX self._scrollX = tmp if pix: if a == 90: self.pos = self.pos[0], self.pos[1] + pix else: self.pos = sigma(self.pos, vec2d(pix, a)) return self
def __init__(self, sk): "Create a new asteroid" super().__init__(self.original) # Random size and mass m = uniform(1, 100) w = pow(m, 1 / 3) / 100 * sk.width # Random position and velocity pos = randint(0, sk.width - 1), -w vel = uniform(0.25, 1) * sk.asteroidSpeed vel = vec2d(vel, uniform(10, 170)) self.config(width=w, wrap=BOTH, pos=pos, vel=vel, spin=uniform(-2, 2), mass=m)
def _checkFront(self): "Update the front color sensor" # Sensor info sw = self.sensorWidth res = 0.5 * self.sensorResolution pos = delta(self.pos, vec2d(-self.radius, self.angle)) # Distance from sensor to edge of sketch obj = prox = None sk = self.sketch if sk.weight: prox = _distToWall(pos, self.angle, self.sensorWidth, *sk.size) if prox: obj = sk # Find closest object within "sensor cone" for gr in self.sensorObjects(sk): if gr is not self and gr.avgColor and hasattr(gr, "rect"): r = gr.radius view = subtend(pos, gr.rect.center, r, None if prox is None else prox + r) if view: # Object may be closer than the current closest object sep, direct, half = view if not res or half > res: # Object size meets sensor resolution threshold beta = abs(angleDifference(self.angle, direct)) - sw if beta < half or sep < r: # Object is in sensor cone pr = sep - r if beta > 0: # CLOSEST point is NOT in sensor cone dr = r + sep * (cos(half * DEG) - 1) pr += dr * (beta / half) ** 2 if prox is None or pr < prox: # Object is closest (so far) prox = pr obj = gr # Save data self.closestObject = obj c = rgba(sk.border if obj is sk else obj.avgColor if obj else (0,0,0)) self.sensorFront = noise(divAlpha(c), self.sensorNoise, 255) self.proximity = None if prox is None else max(0, round(prox))
def _downColor(self): "Calculate downward colour-sensor value" d = self.radius // 8 r = 2 * d size = r, r r = 0.75 * self.radius posn = vsum(vec2d(r, self.angle * DEG), self.posn, (-d, -d)) sk = self.sketch self._downRect = Rect(posn + size) if sk.sprites._debugCollide: Image(size, self.downColor).blitTo(posn=posn) try: if sk.bgImage: srf = sk.scaledBgImage.surface.subsurface(posn + size) else: srf = None if sk.bgColor: img = Image(size, sk.bgColor) if srf: img.surface.blit(srf, (0, 0)) else: img = Image(srf) return noise(img.averageColor(), self.sensorNoise, alpha=255) except: return None
def _detectObstacles(self, cone=0, steps=None, group=None): "Detect obstacles in front of the robot" sk = self.sketch if steps is None: steps = max(1, cone) if group is None: # group = self.spriteList.search(status=VISIBLE) group = [s for s in self.spriteList if s.status == VISIBLE] if sk.wall: group = set(group) | set(self.sketch._wallPoly.segments) obst = {} r = sum(sk.size) stepAngle = cone / steps angle = -cone / 2 while steps >= 0: a = (self.angle + angle) * DEG seg = Segment(self.posn, add(self.posn, vec2d(r, a))) if sk.sprites._debugCollide: line(sk.surface, (0, 0, 0), self.posn, seg.eval()) for s in group: if s is not self: shape = s.shape if isinstance(s, Sprite) else s pts = shape.intersect2d(seg) minSep = None for pt in pts: r2 = distSq(pt, self.posn) if minSep is None or r2 < minSep: minSep = r2 if minSep: obst[None if s is shape else s] = sqrt(minSep) if cone: angle += stepAngle steps -= 1 else: steps = -1 return obst
def onbounce(self, wall): v, a = polar2d(*self.vel) self.vel = vec2d(v, a + uniform(-5, 5)) self.oncollide()