From 7e3a5627fc5052713ce4b843f3d98e3ecc557a57 Mon Sep 17 00:00:00 2001 From: Raymond Hogenson Date: Sun, 9 Oct 2016 21:05:20 -0400 Subject: Add new fish movement scheme --- bridge.py | 6 +- constants.py | 3 + events-example3.py | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++++ fish_show.py | 52 +++++++++++--- 4 files changed, 256 insertions(+), 11 deletions(-) create mode 100755 events-example3.py diff --git a/bridge.py b/bridge.py index 70adb99..757d3c6 100644 --- a/bridge.py +++ b/bridge.py @@ -11,8 +11,8 @@ else: have_lumiversepython = True class Bridge(object): - WIDTH = 200 - HEIGHT = 2 + WIDTH = constants.BRIDGE_WIDTH + HEIGHT = constants.BRIDGE_HEIGHT def __init__(self): if have_lumiversepython: @@ -41,4 +41,4 @@ def main(): #show = SunriseShow(bridge) show.run(framerate=FRAME_RATE) -main() \ No newline at end of file +main() diff --git a/constants.py b/constants.py index 1685c5a..34c6de0 100644 --- a/constants.py +++ b/constants.py @@ -20,3 +20,6 @@ MAX_SATURATION = 1 MIN_SATURATION = 0.5 MIN_VALUE = 0.3 MAX_VALUE = 1 + +BRIDGE_WIDTH = 200 +BRIDGE_HEIGHT = 2 diff --git a/events-example3.py b/events-example3.py new file mode 100755 index 0000000..bbf2d68 --- /dev/null +++ b/events-example3.py @@ -0,0 +1,206 @@ +#!/usr/bin/python +# events-example3.py +# Demos timer, mouse, and keyboard events + +from tkinter import * +import math +import random + +class Vector(object): + """N-dimensional vector class.""" + def __init__(self, *components): + """Create a vector from its components.""" + self.components = components + + def __add__(self, other): + """Add two vectors.""" + return self.__class__( + *map(lambda xy: xy[0] + xy[1], zip(self.components, other.components)) + ) + + def __sub__(self, other): + """Subtract two vectors.""" + return self.__class__( + *map(lambda xy: xy[0] - xy[1], zip(self.components, other.components)) + ) + + def __mul__(self, other): + """Multiply a vector by a constant.""" + return self.__class__(*map(lambda x: x * other, self.components)) + + def __rmul__(self, other): + """Multiply a constant by a vector.""" + return self * other + + def __abs__(self): + """Return the 2-norm of the vector.""" + return math.sqrt(sum(map(lambda x: x * x, self.components))) + + def __truediv__(self, scalar): + """Return self * 1 / scalar.""" + return self * (1 / scalar) + + def __eq__(self, other): + return self.components == other.components + + +def init(data): + data.squareLeft = 50 + data.squareTop = 0 + data.squareFill = "red" + data.squareSize = 25 + data.circleCenters = [ ] + data.counter = 0 + data.headingRight = True + data.headingDown = True + data.isPaused = False + data.timerDelay = 50 + + data.speed = Vector(10, 0) + data.drag = -1 + + data.tailAcceleration = Vector(5, 0) + data.tailDuration = 2 + data.tailMoving = False + data.tailThreshold = 5 + data.tailMoveCount = 0 + +def mousePressed(event, data): + newCircleCenter = (event.x, event.y) + data.circleCenters.append(newCircleCenter) + +def keyPressed(event, data): + if (event.char == "d"): + if (len(data.circleCenters) > 0): + data.circleCenters.pop(0) + else: + print("No more circles to delete!") + elif (event.char == "p"): + data.isPaused = not data.isPaused + elif (event.char == "s"): + doStep(data) + if (event.keysym == "Left"): + moveLeft(data) + elif (event.keysym == "Right"): + moveRight(data) + +def moveLeft(data): + data.squareLeft -= 20 + +def moveRight(data): + data.squareLeft += 20 + +def moveUp(data): + data.squareTop -= 20 + +def moveDown(data): + data.squareTop += 20 + +def timerFired(data): + if (not data.isPaused): doStep(data) + +def fixDirection(data, theta=None): + if theta is None: + theta = random.random() * 2 * math.pi + costheta = math.cos(theta) + sintheta = math.sin(theta) + speedx = data.tailAcceleration.components[0] + speedy = data.tailAcceleration.components[1] + # From en.wikipedia.org/wiki/Rotation_matrix + data.tailAcceleration = Vector( + speedx * costheta - speedy * sintheta, + speedx * sintheta + speedy * costheta + ) + +def doStep(data): + data.counter += 1 + if data.counter % 50 == 0: + fixDirection(data) + #print(abs(data.tailAcceleration)) + data.speed += data.drag * data.speed / abs(data.speed) + if abs(data.speed) < data.tailThreshold: + data.tailMoving = True + if data.tailMoveCount > data.tailDuration: + data.tailMoveCount = 0 + data.tailMoving = False + if data.tailMoving: + data.speed += data.tailAcceleration + data.tailMoveCount += 1 + data.squareLeft += data.speed.components[0] + data.squareTop += data.speed.components[1] + if data.squareLeft + data.squareSize > data.width: + data.squareLeft = 0 + if data.squareLeft < 0: + data.squareLeft = data.width - data.squareSize + if data.squareTop < 0: + data.squareTop = data.height - data.squareSize + if data.squareTop + data.squareSize > data.height: + data.squareTop = 0 + +def redrawAll(canvas, data): + # draw the square + canvas.create_rectangle(data.squareLeft, + data.squareTop, + data.squareLeft + data.squareSize, + data.squareTop + data.squareSize, + fill=data.squareFill) + # draw the circles + for circleCenter in data.circleCenters: + (cx, cy) = circleCenter + r = 20 + canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill="cyan") + # draw the text + canvas.create_text(150,20,text="events-example3.py") + canvas.create_text(150,40,text="Mouse clicks create circles") + canvas.create_text(150,60,text="Pressing 'd' deletes circles") + canvas.create_text(150,80,text="Pressing 'p' pauses/unpauses timer") + canvas.create_text(150,100,text="Pressing 's' steps the timer once") + canvas.create_text(150,120,text="Left arrow moves square left") + canvas.create_text(150,140,text="Right arrow moves square right") + canvas.create_text(150,160,text="Timer changes color of square") + +#################################### +# use the run function as-is +#################################### + +def run(width=300, height=300): + def redrawAllWrapper(canvas, data): + canvas.delete(ALL) + redrawAll(canvas, data) + canvas.update() + + def mousePressedWrapper(event, canvas, data): + mousePressed(event, data) + redrawAllWrapper(canvas, data) + + def keyPressedWrapper(event, canvas, data): + keyPressed(event, data) + redrawAllWrapper(canvas, data) + + def timerFiredWrapper(canvas, data): + timerFired(data) + redrawAllWrapper(canvas, data) + # pause, then call timerFired again + canvas.after(data.timerDelay, timerFiredWrapper, canvas, data) + # Set up data and call init + class Struct(object): pass + data = Struct() + data.width = width + data.height = height + data.timerDelay = 100 # milliseconds + init(data) + # create the root and the canvas + root = Tk() + canvas = Canvas(root, width=data.width, height=data.height) + canvas.pack() + # set up events + root.bind("", lambda event: + mousePressedWrapper(event, canvas, data)) + root.bind("", lambda event: + keyPressedWrapper(event, canvas, data)) + timerFiredWrapper(canvas, data) + # and launch the app + root.mainloop() # blocks until window is closed + print("bye!") + +run(600, 1000) diff --git a/fish_show.py b/fish_show.py index cda4174..f3ccf25 100644 --- a/fish_show.py +++ b/fish_show.py @@ -18,15 +18,51 @@ class Fish(object): self.speed = step self.step = step + self.speed = Vector(10, 0) + self.drag = -1 + + self.tailAcceleration = color.Vector(5, 0) + self.tailDuration = 2 + self.tailMoving = False + self.tailThreshold = 5 + self.tailMoveCount = 0 + def update(self): """Swim gently to the other side of the bridge.""" - self.x += self.step - if self.x > 200: - self.x = 0 - self.y = 0.25 + 0.25 * math.sin(math.pi * self.x / 10) - #theta = random.random() * math.pi / 2 - math.pi / 4 - #self.y += 0.01 * math.sin(theta) - #print(self.y) + self.speed += self.drag * self.speed / abs(self.speed) + if abs(self.speed) < self.tailThreshold: + self.tailMoving = True + if self.tailMoveCount > self.tailDuration: + self.tailMoveCount = 0 + self.tailMoving = False + if self.tailMoving: + self.speed += self.tailAcceleration + self.tailMoveCount += 1 + self.x += self.speed.components[0] + self.y += self.speed.components[1] + # + # THIS NEEDS TO CHANGE + # + if self.x > constants.BRIDGE_WIDTH: + self.x = -self.width + if self.x < -self.width: + self.x = constants.BRIDGE_WIDTH + if self.y < -self.width: + self.y = constants.BRIDGE_HEIGHT + self.height + if self.y > -self.height: + self.y = constants.BRIDGE_HEIGHT + + def fix_direction(self, theta=None): + if theta is None: + theta = random.random * math.pi / 2 - math.pi / 4 + costheta = math.cos(theta) + sintheta = math.sin(theta) + speedx = self.tailAcceleration.components[0] + speedy = self.tailAcceleration.components[1] + data.tailAcceleration = color.Vector( + speedx * costheta - speedy * sintheta, + speedx * sintheta + speedy * costheta + ) class Light(object): """Represent a bridge light and its state.""" @@ -114,4 +150,4 @@ class FishShow(Show): def update(self): self.fish.update() for light in self.lights: - light.update(self.fish) \ No newline at end of file + light.update(self.fish) -- cgit v1.3.1 From 9b60deedc508c74b249fd1da0776b5a719322807 Mon Sep 17 00:00:00 2001 From: group1 Date: Sun, 9 Oct 2016 23:13:51 -0400 Subject: Realistic small fish movement --- bridge.py | 4 ++-- color.py | 13 ++++++++++++- fish_show.py | 50 ++++++++++++++++++++++++++++---------------------- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/bridge.py b/bridge.py index 757d3c6..b7e4576 100644 --- a/bridge.py +++ b/bridge.py @@ -11,8 +11,8 @@ else: have_lumiversepython = True class Bridge(object): - WIDTH = constants.BRIDGE_WIDTH - HEIGHT = constants.BRIDGE_HEIGHT + WIDTH = BRIDGE_WIDTH + HEIGHT = BRIDGE_HEIGHT def __init__(self): if have_lumiversepython: diff --git a/color.py b/color.py index 058abb4..c65252b 100644 --- a/color.py +++ b/color.py @@ -7,7 +7,7 @@ class Vector(object): __slots__ = ('x', 'y', 'z') """3-dimensional vector class.""" - def __init__(self, x, y, z): + def __init__(self, x=0, y=0, z=0): """Create a vector from its components.""" self.x = x self.y = y @@ -40,6 +40,17 @@ class Vector(object): def __eq__(self, other): return self.x == other.x and self.y == other.y and self.z == other.z + def unit(self): + return self / abs(self) + + def rot2d(self, theta): + costheta = math.cos(theta) + sintheta = math.sin(theta) + new_x = self.x * costheta - self.y * sintheta + new_y = self.x * sintheta + self.y * costheta + return self.__class__(new_x, new_y) + + @property def components(self): return (self.x, self.y, self.z) diff --git a/fish_show.py b/fish_show.py index f3ccf25..7cd8300 100644 --- a/fish_show.py +++ b/fish_show.py @@ -1,5 +1,6 @@ from __future__ import division import color +from color import Vector import random import constants from constants import * @@ -18,20 +19,23 @@ class Fish(object): self.speed = step self.step = step - self.speed = Vector(10, 0) - self.drag = -1 + self.speed = Vector(0.1, 0) + self.drag = -0.09 - self.tailAcceleration = color.Vector(5, 0) + self.tailAccelMag = 0.4 + self.tailAcceleration = Vector(self.tailAccelMag, 0) self.tailDuration = 2 self.tailMoving = False - self.tailThreshold = 5 + self.tailThreshold = 0.7 self.tailMoveCount = 0 def update(self): """Swim gently to the other side of the bridge.""" - self.speed += self.drag * self.speed / abs(self.speed) + self.speed += self.drag * self.speed if abs(self.speed) < self.tailThreshold: self.tailMoving = True + self.tailThreshold = rand_in_range(0.5, 1.4) + self.fix_direction() if self.tailMoveCount > self.tailDuration: self.tailMoveCount = 0 self.tailMoving = False @@ -47,22 +51,21 @@ class Fish(object): self.x = -self.width if self.x < -self.width: self.x = constants.BRIDGE_WIDTH - if self.y < -self.width: - self.y = constants.BRIDGE_HEIGHT + self.height - if self.y > -self.height: - self.y = constants.BRIDGE_HEIGHT - - def fix_direction(self, theta=None): - if theta is None: - theta = random.random * math.pi / 2 - math.pi / 4 - costheta = math.cos(theta) - sintheta = math.sin(theta) - speedx = self.tailAcceleration.components[0] - speedy = self.tailAcceleration.components[1] - data.tailAcceleration = color.Vector( - speedx * costheta - speedy * sintheta, - speedx * sintheta + speedy * costheta - ) + + print(self.x, self.y) + + def fix_direction(self): + min_theta = -0.32 + max_theta = 0.32 + if self.y < 0: + min_theta = 0 + if self.y > 1: + max_theta = 0 + + #theta = rand_in_range(min_theta, max_theta) + theta = random.gauss((max_theta+min_theta)/2, (max_theta - min_theta)/12) + self.tailAcceleration = Vector(self.tailAccelMag, 0).rot2d(theta) + class Light(object): """Represent a bridge light and its state.""" @@ -144,10 +147,13 @@ class FishShow(Show): self.lights = [Light(x, y, self.bridge.get_light(x, y)) for y in xrange(self.bridge.HEIGHT) for x in xrange(self.bridge.WIDTH)] - self.fish = Fish(0, 0, 23, 1.5, color.black, 1) + self.fish = Fish(0, 0.5, 23, 1.5, color.black, 1) def update(self): self.fish.update() for light in self.lights: light.update(self.fish) + +def rand_in_range(low, high): + return random.random() * (high - low) + low -- cgit v1.3.1