diff --git a/mesa/agent.py b/mesa/agent.py index 6d3d67919c6..08937bd112c 100644 --- a/mesa/agent.py +++ b/mesa/agent.py @@ -28,6 +28,7 @@ def __init__(self, unique_id: int, model: "Model") -> None: self.unique_id = unique_id self.model = model self.pos: Optional[Position] = None + self.heading = 90 def step(self) -> None: """A single step of the agent.""" @@ -36,6 +37,81 @@ def step(self) -> None: def advance(self) -> None: pass + def move_forward_or_backward(self, amount, sign): + """Does the calculation to find the agent's next move and is used within the forward and backward functions""" + new_x = float(self.pos[0]) + sign * math.cos(self.heading * math.pi / 180) * amount + new_y = float(self.pos[1]) + sign * math.sin(self.heading * math.pi / -180) * amount + next_pos = (new_x, new_y) + try: + self.model.space.move_agent(self, next_pos) + except: + print("agent.py (forward_backwards): could not move agent within self.model.space") + + def move_forward(self, amount): + """Moves the agent forward by the amount given""" + self.move_forward_or_backward(amount, 1) + + def move_backward(self, amount): + """Moves the agent backwards from where its facing by the given amount""" + self.move_forward_or_backward(amount, -1) + + def turn_right(self, degree): + """Turns the agent right by the given degree""" + self.heading = (self.heading - degree) % 360 + + def turn_left(self, degree): + """Turns the agent left by the given degree""" + self.heading = (self.heading + degree) % 360 + + def setxy(self, x, y): + """Sets the current position to the specified x,y parameters""" + self.pos = (x, y) + + def set_pos(self, apos): + """Sets the current position to the specified pos parameter""" + self.pos = apos + + def distancexy(self, x, y): + """Gives you the distance of the agent and the given coordinate""" + return math.dist(self.pos, (x, y)) + + def distance(self, another_agent): + """Gives you the distance between the agent and another agent""" + return self.distancexy(another_agent.pos[0], another_agent.pos[1]) + + def die(self): + """Removes the agent from the schedule and the grid """ + try: + self.model.schedule.remove(self) + except: + print("agent.py (die): could not remove agent from self.model.schedule") + try: + self.model.space.remove_agent(self) + except: + print("agent.py (die): could not remove agent from self.model.space") + + def towardsxy(self, x, y): + """Calculates angle between a given coordinate and horizon as if the current position is the origin""" + dx = x - float(self.pos[0]) + dy = float(self.pos[1]) - y + if dx == 0: + return 90 if dy > 0 else 270 + else: + return math.degrees(math.atan2(dy, dx)) + + def towards(self, another_agent): + """Calculates angle between an agent and horizon as if the current position is the origin""" + return self.towardsxy(*another_agent.pos) + + def facexy(self, x, y): + """Makes agent face a given coordinate""" + self.heading = self.towardsxy(x, y) + + def face(self, another_agent): + """Makes agent face another agent""" + x, y = another_agent.pos + self.facexy(x, y) + @property def random(self) -> Random: return self.model.random diff --git a/mesa/visualization/modules/SimpleContinuousModule.py b/mesa/visualization/modules/SimpleContinuousModule.py new file mode 100644 index 00000000000..7898dd07315 --- /dev/null +++ b/mesa/visualization/modules/SimpleContinuousModule.py @@ -0,0 +1,34 @@ +from mesa.visualization.ModularVisualization import VisualizationElement + + +class SimpleCanvas(VisualizationElement): + local_includes = ["simple_continuous_canvas.js"] + portrayal_method = None + canvas_height = 500 + canvas_width = 500 + + def __init__(self, portrayal_method, canvas_height=500, canvas_width=500, background_src=None): + """ + Instantiate a new SimpleCanvas + """ + self.portrayal_method = portrayal_method + self.canvas_height = canvas_height + self.canvas_width = canvas_width + self.background_src = background_src + new_element = "new Simple_Continuous_Module({}, {}, {})".format( + self.canvas_width, self.canvas_height, "'" + self.background_src + "'" if self.background_src != None else "null" + ) + self.js_code = "elements.push(" + new_element + ");" + + def render(self, model): + space_state = [] + for obj in model.schedule.agents: + portrayal = self.portrayal_method(obj) + x, y = obj.pos + x = (x - model.space.x_min) / (model.space.x_max - model.space.x_min) + y = (y - model.space.y_min) / (model.space.y_max - model.space.y_min) + portrayal["x"] = x + portrayal["y"] = y + portrayal["heading"] = obj.heading + space_state.append(portrayal) + return space_state \ No newline at end of file diff --git a/mesa/visualization/modules/simple_continuous_canvas.js b/mesa/visualization/modules/simple_continuous_canvas.js new file mode 100644 index 00000000000..4d50486a8cc --- /dev/null +++ b/mesa/visualization/modules/simple_continuous_canvas.js @@ -0,0 +1,332 @@ +var ContinuousVisualization = function(height, width, context, background_src=null) { + var height = height; + var width = width; + var context = context; + var background = new Image(); + var background_src = background_src; + + this.draw = function(objects) { // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes + for (var i in objects) { + var p = objects[i]; + if (p.Shape == "rectangle") + this.drawRectangle(p.x, p.y, p.w, p.h, p.Color, p.Filled); + if (p.Shape == "circle"){ + this.drawCircle(p.x, p.y, p.r, p.Color, false); + this.drawRadius(p.x, p.y, p.r, p.heading, p.Color) + } + if (p.Shape == "default"){ + this.drawDefault(p.x, p.y, p.r, p.heading, p.Color) + } + if(p.Shape == "triangle"){ + this.drawTriangle(p.x, p.y, p.r, p.heading, p.Color) + } + if(p.Shape == "star"){ + this.drawStar(p.x, p.y, p.r, p.heading, p.Color) + } + if(p.Shape == "smile"){ + this.drawSmileFace(p.x, p.y, p.r, p.heading, p.Color) + } + if(p.Shape == "pentagon"){ + this.drawPentagon(p.x, p.y, p.r, p.heading, p.Color) + } + if(p.Shape == "arrow"){ + this.drawArrow(p.x, p.y, p.r, p.heading, p.Color) + } + if(p.Shape == "plane"){ + this.drawPlane(p.x, p.y, p.r, p.heading, p.Color) + } + if (p.Shape == "ant"){ + this.drawAnt(p.x, p.y, p.r, p.heading, p.Color) + } + } + }; + this.drawAnt = function(x,y, radius, heading, color){ + var cx = x * width; + var cy = y * height; + var r = radius; + context.save(); + context.strokeStyle = color; + context.fillStyle = color; + context.beginPath(); + context.translate( cx, cy); + context.scale(1,-1); + context.rotate(heading*Math.PI/180); + //put points into here: + context.arc(0, 0, r/6, Math.PI/2, 5 * Math.PI/2, false); + context.moveTo((r/-5), 0); + context.arc(r/-5, 0, r/5, Math.PI/2, 5 * Math.PI/2, false); + context.moveTo((r/5),0); + context.arc((r/5),0, r/6, Math.PI/2, 5 * Math.PI/2, false); + context.moveTo((r/4), (r/10)); + context.lineTo((r/2), (r/4)); + context.moveTo((r/4), (r/-10)); + context.lineTo((r/2), (r/-4)); + //put points above here + context.fill(); + context.stroke(); + context.restore(); + }; + this.drawPlane = function(x, y, radius, heading, color){ + var cx = x * width; + var cy = y * height; + var r = radius; + context.save(); + + context.strokeStyle = color; + context.fillStyle = color; + context.beginPath(); + context.translate( cx, cy); + context.scale(1,-1); + context.rotate(heading*Math.PI/180); + //add points bellow here + + context.moveTo(r, 0) + context.lineTo((r*3/4), (r/8)) //1 + context.lineTo((r*4/6), (r/8)) //2 + context.lineTo((r*2/5), (r/2)) //3 + context.lineTo((r*2/7), (r/2)) //4 + context.lineTo((r*2/7), (r/8)) //5 + context.lineTo((r/6), (r/12)) //6 + context.lineTo((r/12), (r/6)) //7 + context.lineTo((r/8), 0) //8 + context.lineTo((r/12), (-r/6)) //9 + context.lineTo((r/6), (-r/12)) + context.lineTo((r*2/7), (-r/8)) + context.lineTo((r*2/7), (-r/2)) + context.lineTo((r*2/5), (-r/2)) + context.lineTo((r*4/6), (-r/8)) + context.lineTo((r*3/4), (-r/8)) + + //add points above here + context.fill(); + context.stroke(); + context.restore(); + } + this.drawArrow = function(x, y, radius, heading, color){ + var cx = x * width; + var cy = y * height; + var r = radius; + context.save(); + context.strokeStyle = color; + context.fillStyle = color; + context.beginPath(); + context.translate( cx, cy); + context.scale(1,-1); + context.rotate(heading*Math.PI/180); + //place coordinates bellow + context.moveTo(r, 0) + context.lineTo((r*2/3), (r/4)) + context.lineTo((r*2/3), (r/11)) + context.lineTo((r/3), (r/11)) + context.lineTo((r/3), (-r/11)) + context.lineTo((r*2/3),(-r/11)) + context.lineTo((r*2/3),(-r/4)) + context.fill(); + context.stroke(); + context.restore(); + } + //feedback on how pentagon looks + this.drawPentagon = function(x, y, radius, heading, color){ + var cx = x * width; + var cy = y * height; + var r = radius; + context.save(); + context.strokeStyle = color; + context.fillStyle = color; + context.beginPath(); + context.translate( cx, cy); + context.scale(1,-1); + context.rotate(heading*Math.PI/180); + + //place bellow here + context.moveTo(r, 0) + context.lineTo((r/2), (-r*13/15)) + context.lineTo((-r*8/11), (-r*8/13)) + context.lineTo((-r*8/11), (r*8/13)) + context.lineTo((r/2),(r*13/15)) + //points above here + + context.fill(); + context.stroke(); + context.restore(); + }; + //feedback on how smileyface looks + this.drawSmileFace = function(x, y, radius, heading, color){ + var cx = x * width; + var cy = y * height; + var r = radius; + context.save(); + context.strokeStyle = color; + context.fillStyle = color; + context.beginPath(); + context.translate( cx, cy); + context.scale(1,-1); + context.rotate(heading*Math.PI/180); + //put points into here: + context.arc(0, 0, r/2, Math.PI/2, 5 * Math.PI/2, false); //Outer layer + context.moveTo(0, (r/3)) + context.arc(0, 0, r/3, Math.PI/2, 3 * Math.PI/2, true); //Smile + context.moveTo(0, (r/-6)); // x = r/4 && y = (r * -0.416) + context.arc(r/-6, r/-6, r/8, 0, Math.PI * 2, true) // left eye + context.moveTo(0, (r/6)) + context.arc(r/-6, r/6, r/8, 0, Math.PI * 2, true) // right eye + //put points above here + context.fill(); + //context.stroke(); + context.restore(); + } + //feedback on how star looks + this.drawStar = function(x, y, radius, heading, color){ + var cx = x * width; + var cy = y * height; + var r = radius; + context.save(); + context.strokeStyle = color; + context.fillStyle = color; + context.beginPath(); + context.translate( cx, cy); + context.scale(1,-1); + context.rotate(heading*Math.PI/180); + //put points into here: + context.moveTo((r/2),0) + context.lineTo((r/5), (r/10)) + context.lineTo((r/5), (r/2)) // extended arm + context.lineTo((r/-20), (r/6)) + context.lineTo((r/-3), (r/4)) + context.lineTo((r/-6), 0) + context.lineTo((r/-3), (r/-4)) + context.lineTo((r/-20), (r/-6)) + context.lineTo((r/5), (r/-2)) // extended arm + //context.lineTo((r/-10), (r/5)) + context.lineTo((r/5), (r/-10)) + context.lineTo((r/2),0) + //put points above here + context.fill(); + context.stroke(); + context.restore(); + }; + this.drawTriangle = function(x,y, radius, heading, color){ + var cx = x * width; + var cy = y * height; + var r = radius; + context.save(); + context.strokeStyle = color; + context.fillStyle = color; + context.beginPath(); + context.translate( cx, cy); + context.scale(1,-1); + context.rotate(heading*Math.PI/180); + //put points into here: + context.moveTo((r/2), 0) + context.lineTo((r/-3), (r/-3)) + context.lineTo((r/-3), (r/3)) + //put points above here + context.fill(); + context.restore(); + }; + this.drawDefault = function(x,y, radius, heading, color){ + //p1 (r,0) + //p2 (-r/2, r/4) + //p3 (0,0) + //p4 (-r/2, -r/4) + var cx = x * width; + var cy = y * height; + var r = radius; + context.save(); + context.strokeStyle = color; + context.fillStyle = color; + context.beginPath(); + context.translate(cx, cy); + context.scale(1,-1); + context.rotate(heading*Math.PI/180); + //put points into here: + context.moveTo((r/2) , 0 ); + context.lineTo((r/-3) , (r/4)); + context.lineTo(0 ,0 ); + context.lineTo((r/-3) , (r/-4)); + //put points above here + context.fill(); + context.restore(); + }; + this.drawCircle = function(x, y, radius, color, fill) { + var cx = x * width; + var cy = y * height; + var r = radius; + context.strokeStyle = color; + context.fillStyle = color; + context.beginPath(); + context.arc(cx, cy, r, 0, Math.PI * 2, false); + context.closePath(); + context.stroke(); + if (fill) { + context.fillStyle = color; + context.fill(); + } + }; + this.drawRadius = function(x, y, radius, heading, color) { + var cx = x * width; + var cy = y * height; + var r = radius; + context.save() + context.strokeStyle = color; + context.fillStyle = color; + context.beginPath(); + context.translate( cx, cy); + context.scale(1,-1); + context.rotate(heading*Math.PI/180); + context.fillRect(0, 0, 0 + r, 2); + context.strokeStyle = color; + context.stroke(); + context.restore(); + }; + + this.drawRectangle = function(x, y, w, h, color, fill) { + context.beginPath(); + var dx = w * width; + var dy = h * height; + // Keep the drawing centered: + var x0 = (x*width) - 0.5*dx; + var y0 = (y*height) - 0.5*dy; + context.strokeStyle = color; + context.fillStyle = color; + if (fill) + context.fillRect(x0, y0, dx, dy); + else + context.strokeRect(x0, y0, dx, dy); + }; + + this.resetCanvas = function() { + context.clearRect(0, 0, height, width); + context.beginPath(); + if(background_src != null){ + this.drawBackground(); + } + }; + this.drawBackground = function(){ + context.globalAlpha = .5; + background.src = background_src; + context.drawImage(background,0,0, 500, 500); + context.globalAlpha = 1; + } +}; + +var Simple_Continuous_Module = function(canvas_width, canvas_height, background_src) { + // Create the element + // ------------------ + // Create the tag: + var canvas_tag = "