def roam(self):
        self.hunger += Boid.hungerRate
        # Flocking (with arbitrary weight)
        steer = self.align() * 1.1
        steer += self.cohesion() * 0.9
        steer += self.separation() * 1.
        steer = vu.capMag(steer, Boid.maxForce * 2.)

        return steer
    def manageHunger(self):
        hungry = (
            self.hunger >= Predator.hungerThld
            or (self.eating is not None and self.hunger > Predator.hungerRate)
        )  # Or eating but not full

        self.maxSpeed = Predator.maxRoamSpeed
        if hungry:
            self.maxSpeed = Predator.maxHungrySpeed
            # Still eating (immobile)
            if self.eating is not None:
                if self.eatCounter < Predator.eatDuration:
                    self.eatCounter += 1
                    if self.eatCounter % 10 == 0:  # Blinking
                        if (self.eatCounter // 10) % 2 == 0:
                            self.canvas.itemconfig(self.eating.id,
                                                   state='normal')
                        else:
                            self.canvas.itemconfig(self.eating.id,
                                                   state='hidden')
                else:
                    self.hunger -= Predator.nrgPerBoid
                    if DEBUG_LOG: print('Eaten boid:', self.id)
                    self.eating.killed()
                    self.eating = None
                    self.eatCounter = 0
                self.acc *= 0.
                self.vxt *= 0.
            # Look for boid
            else:
                closestBoid = self.getClosestBoid()
                if closestBoid is None:  # Keep looking
                    self.acc += self.roam()
                else:
                    self.maxSpeed = Predator.maxHuntSpeed
                    sqrDist = closestBoid.distanceSqr(self)
                    # Close enough to eat
                    if sqrDist <= PRED_HH**2:
                        self.eating = closestBoid
                        self.eatCounter = 1
                        closestBoid.caught = True
                        print('Caught pred:', self.id)
                        self.acc *= 0.
                        self.vxt *= 0.
                    # Go toward boid
                    else:
                        self.hunger += Predator.hungerRate

                        steer = vu.wrapRelativePos(self.pos,
                                                   closestBoid.pos) - self.pos
                        steer = vu.setMag(steer, Predator.maxHuntSpeed)
                        steer -= self.vel
                        steer = vu.capMag(steer, Predator.maxForce * 4.)
                        self.acc = steer

        return hungry
    def manageHunger(self):
        hungry = (self.hunger >= Boid.hungerThld
                  or (self.eating is not None and self.hunger > 0)
                  )  # Or eating but not full

        if hungry:
            bite = -1.
            if self.eating is not None: bite = self.eating.takeBite()

            # Still eating (immobile)
            if bite > 0.:
                self.hunger -= bite * Boid.floraNrgRatio
                self.acc *= 0.
                self.vxt *= 0.
            # Look for flora
            else:
                self.eating = None

                closestFlora = self.getBiggestClosestFlora()
                if closestFlora is None:  # Keep looking
                    self.acc += self.roam()
                else:
                    sqrDist = self.distanceSqr(closestFlora)
                    nearDist = max(BOID_HH**2, closestFlora.rad**2)
                    # Close enough to eat
                    if sqrDist <= nearDist:
                        bite = closestFlora.takeBite()
                        if bite > 0.:
                            self.hunger -= bite * Boid.floraNrgRatio
                            self.eating = closestFlora
                            self.acc *= 0.
                            self.vxt *= 0.
                    # Go toward flora
                    else:
                        self.hunger += Boid.hungerRate

                        steer = vu.wrapRelativePos(self.pos,
                                                   closestFlora.pos) - self.pos
                        steer = vu.setMag(steer, Boid.maxRoamSpeed)
                        steer -= self.vel
                        steer = vu.capMag(steer, Boid.maxForce * 2)
                        self.acc = steer

        return hungry
    def align(self):
        steer = np.array([0., 0.])
        sum = np.array([0., 0.])
        nbr = 0

        cells = self.getNearCells()
        for cell in cells:
            for boid in Boid.grid[cell[0]][cell[1]]:
                if boid is not self and boid.eating is None and self.distanceSqr(
                        boid) <= VISION_RADSQR:
                    sum += boid.vel
                    nbr += 1
        # Apply
        if nbr > 0:
            steer = sum / nbr
            steer = vu.setMag(steer, Boid.maxRoamSpeed)
            steer -= self.vel
            steer = vu.capMag(steer, Boid.maxForce)

        return steer
    def separation(self):
        steer = np.array([0., 0.])
        sum = np.array([0., 0.])
        nbr = 0

        cells = self.getNearCells()
        for cell in cells:
            for boid in Boid.grid[cell[0]][cell[1]]:
                sqrDist = self.distanceSqr(boid)
                if boid is not self and boid.eating is None and sqrDist <= VISION_RADSQR * 0.5:
                    if sqrDist < EPSILON: sqrDist = EPSILON
                    diff = self.pos - vu.wrapRelativePos(self.pos, boid.pos)
                    sum += vu.setMag(diff, 1. / sqrDist)
                    nbr += 1
        # Apply
        if nbr > 0:
            steer = sum / nbr
            steer = vu.setMag(steer, Boid.maxRoamSpeed)
            steer -= self.vel
            steer = vu.capMag(steer, Boid.maxForce)

        return steer
    def updatePos(self):
        # Update state
        self.acc *= self.inertiaRatio

        # Reproduction
        self.manageReproduction()

        # Show stoppers
        if self.caught or self.starved():
            return

        # Escape predator
        escaping = self.managePredators()

        if not escaping:
            self.maxSpeed = Boid.maxRoamSpeed

            # Hungry
            hungry = self.manageHunger()

            # Roam free
            if not hungry:
                self.eating = None
                self.mating()
                self.acc += self.roam()
        else:
            self.eating = None
            self.maxSpeed = Boid.maxEscapeSpeed

        # Special case: immobile -> random push
        if self.eating is None and vu.norm(self.acc) < EPSILON and vu.norm(
                self.vxt) < EPSILON:
            self.acc = vu.randomUnitVector() * Boid.maxForce
            # if DEBUG_LOG: print('Random push:', self.id)
        # No deceleration if not at full speed
        if vu.norm(self.vxt) < self.maxSpeed:
            self.inertiaRatio = 1.
        else:
            self.inertiaRatio = Boid.inertiaRatio

        # Apply acceleration
        self.acc = vu.capMag(self.acc, Boid.maxForce * 8.)  # Saturate acc
        self.vxt += self.acc
        self.vxt = vu.capMag(self.vxt, self.maxSpeed)  # Saturate vel
        self.nxt = np.add(self.nxt, self.vxt)

        # Update position + rotation
        if ((self.acc[0] != 0. or self.acc[1] != 0.)
                and (self.vxt[0] != 0. or self.vxt[1] != 0)):
            # Warp around
            vu.wrapEdges(self)

            self.points = vu.updateTriPoints(self.nxt, self.vel, BOID_HH,
                                             BOID_HW)
        # Update position only
        else:
            # Move
            self.points = [
                self.points[0] + self.vxt[0], self.points[1] + self.vxt[1],
                self.points[2] + self.vxt[0], self.points[3] + self.vxt[1],
                self.points[4] + self.vxt[0], self.points[5] + self.vxt[1]
            ]
            # Warp around
            vu.wrapEdges(self, True)

        # Apply
        if (self.pos[0] != self.nxt[0] or self.pos[1] != self.nxt[1]
                or self.vel[0] != self.vxt[0] or self.vel[1] != self.vxt[1]):
            self.canvas.coords(self.id, *self.points)
            self.canvas.update_idletasks()