| <html> | |
| <head> | |
| <title>AgentScript Model</title> | |
| <script src="https://raw.github.com/backspaces/agentscript/master/lib/agentscript.js"></script> | |
| <script src="https://raw.github.com/jashkenas/coffee-script/master/lib/coffee-script/coffee-script.js"></script> | |
| <script src="http://concord-consortium.github.com/lab/vendor/d3/d3.js"></script> | |
| <script src="http://concord-consortium.github.com/lab/vendor/jquery/jquery.min.js"></script> | |
| <script src="http://concord-consortium.github.com/lab/lab/lab.grapher.js"></script> | |
| <script type="text/coffeescript"> | |
| class ClimateModel extends ABM.Model | |
| u = ABM.util # static variable | |
| setup: -> # called by Model ctor | |
| @refreshPatches = true | |
| @agents.setDefaultShape "arrow" | |
| @agentBreeds "sunrays heat IR CO2 clouds" | |
| # globals | |
| @sunBrightness = 100 | |
| @albedo = 0.3 | |
| @temperature = 13 | |
| @agentSize = 0.75 | |
| @skyTop = (15) - 5 | |
| @earthTop = 8 + (-15) | |
| @sunlightHeading = -1.1 | |
| @numClouds = 0 | |
| # allow access to this model from window.ClimateModel | |
| window.ClimateModel = this | |
| @spacePatches = (p for p in @patches when p.y == (15)) | |
| @skyTopPatches = (p for p in @patches when p.y < (15) && p.y > @skyTop) | |
| @skyPatches = (p for p in @patches when p.y <= @skyTop && p.y > @earthTop) | |
| @earthSurfacePatches = (p for p in @patches when p.y == @earthTop) | |
| @earthPatches = (p for p in @patches when p.y < @earthTop) | |
| p.color = [0, 0, 0] for p in @spacePatches | |
| for p in @skyTopPatches | |
| p.color = [196, 196, 196] if p.y == @skyTop + 1 | |
| p.color = [128, 128, 128] if p.y == @skyTop + 2 | |
| p.color = [64, 64, 64] if p.y == @skyTop + 3 | |
| p.color = [32, 32, 32] if p.y == @skyTop + 4 | |
| p.color = [100, 150, 255] for p in @skyPatches | |
| p.color = [255, 200, 200] for p in @earthPatches | |
| @updateAlbedoOfSurface() | |
| @createCO2(4) | |
| setAlbedo: (percent) -> | |
| @albedo = percent | |
| @updateAlbedoOfSurface() | |
| getAlbedo : -> | |
| @albedo | |
| setSunBrightness: (val) -> | |
| @sunBrightness = val | |
| getSunBrightness : -> | |
| @sunBrightness | |
| getTemperature : -> | |
| @temperature | |
| getCO2Count : -> | |
| @CO2().length | |
| addCO2: -> | |
| @createCO2(1) | |
| subtractCO2: -> | |
| if @CO2().length > 0 | |
| @CO2().oneOf().die() | |
| updateAlbedoOfSurface: -> | |
| p.color = [Math.floor(196 * @albedo), Math.floor(255 * @albedo), Math.floor(196 * @albedo)] for p in @earthSurfacePatches | |
| reflectOffHorizontalPlane: (a) -> | |
| heading = a.heading | |
| newheading = heading | |
| if heading > Math.PI | |
| newheading = Math.PI - (heading - Math.PI) | |
| if heading < Math.PI | |
| newheading = Math.PI + (Math.PI - heading) | |
| else | |
| newheading = 0 | |
| a.heading = newheading | |
| headingUp: (a) -> | |
| heading = a.heading | |
| heading > 0 && heading < Math.PI | |
| transformToIR: (a) -> | |
| a.breed = "IR" | |
| a.heading = -@sunlightHeading | |
| # a.heading = u.randomFloat2(2.6, 0.5) | |
| # a.heading = u.randomCentered(Math.PI/4) + Math.PI/2 | |
| a.color = [200, 32, 200] | |
| a.shape = "arrow" | |
| transformToHeat: (a) -> | |
| a.breed = "heat" | |
| a.y = @earthTop-1 | |
| a.heading = u.randomFloat2(-0.5, -Math.PI+0.5) | |
| a.shape = "circle" | |
| randomLightness = u.randomInt2(32, 128) | |
| a.color = [255, randomLightness, randomLightness] | |
| # | |
| # CO2 | |
| # | |
| createCO2: (num) -> | |
| while num > 0 | |
| num-- | |
| @agents.create 1, (a) => | |
| a.breed = "CO2" | |
| a.size = @agentSize | |
| a.color = [0, 255, 0] | |
| a.shape ="pentagon" | |
| a.heading = u.randomCentered(Math.PI) | |
| a.setXY u.randomCentered(22), u.randomFloat2(@earthTop+1, @skyTop) | |
| runCO2: -> | |
| for a in @CO2() | |
| if a | |
| a.heading = a.heading + u.randomCentered(Math.PI/9) | |
| a.forward 0.1 | |
| if a.y <= (-14) | |
| a.heading = u.randomFloat2(0.1, Math.PI-0.1) | |
| if a.y <= @earthTop + 1 | |
| a.heading = u.randomFloat2(Math.PI/4, Math.PI*3/4) | |
| if a.y >= @skyTop + 1 | |
| a.heading = u.randomFloat2(-Math.PI/4, -Math.PI*3/4) | |
| # | |
| # IR | |
| # | |
| runIR: -> | |
| for a in @IR() | |
| if a | |
| a.forward 0.5 | |
| if @CO2().inRadius(a, 1).any() | |
| a.heading = u.randomFloat2(-Math.PI/4, -Math.PI*3/4) | |
| a.die() if a.heading == -@sunlightHeading && a.y > (14) | |
| if a.y <= @earthTop | |
| @transformToHeat(a) | |
| # | |
| # Heat | |
| # | |
| runHeat: -> | |
| @updateTemperature() | |
| for a in @heat() | |
| if a | |
| a.heading = a.rotate(u.randomCentered(0.3)) | |
| a.forward u.randomFloat2(0.05, 0.2) | |
| if a.y <= (-15) | |
| a.heading = u.randomFloat2(0.1, Math.PI-0.1) | |
| if a.y >= @earthTop | |
| if @returnToSky | |
| @transformToIR(a) | |
| else | |
| a.heading = u.randomCentered(2) | |
| returnToSky: -> | |
| u.randomInt(100) < (temperature * 20) && u.randomInt(20) < 2 | |
| updateTemperature: -> | |
| @temperature = 0.99 * @temperature + 0.01 * (-7 + 0.5 * @heat().length) | |
| leaveToSpace: (a) -> | |
| heading = a.heading % Math.PI | |
| ypos = a.y | |
| if heading < Math.PI && heading > 0 | |
| if ypos < (-15) || ypos >= (15) | |
| a.die() | |
| # | |
| # Clouds | |
| # | |
| addCloud: -> | |
| @numClouds++ | |
| @setupClouds(@numClouds) | |
| subtractCloud: -> | |
| if @clouds().length | |
| cloudNum = @clouds().oneOf().cloudNum | |
| for a in @clouds() | |
| a.die() if a.cloudNum == cloudNum | |
| setupClouds: (num) -> | |
| for a in @clouds() | |
| a.die() | |
| i = 0 | |
| while i < num | |
| @makeCloud(i, num) | |
| i++ | |
| makeCloud: (cloudNum, total) -> | |
| width = @skyTop - @earthTop | |
| mid = (@skyTop + @earthTop)/2 | |
| y = mid + width * ((cloudNum/total) - 0.3) - 2 | |
| y = 6 if cloudNum == 0 | |
| x = 2 * u.randomFloat(24) + -24 | |
| cloudParts = 3 + u.randomInt(16) | |
| while cloudParts-- | |
| @agents.create 1, (a) => | |
| a.breed = "clouds" | |
| a.cloudNum = cloudNum | |
| a.color = [255,255,255] | |
| a.size = @agentSize + 0.5 + u.randomFloat(1) | |
| a.shape = "circle" | |
| a.heading = 0 | |
| a.setXY x + u.randomFloat(5) - 4, y + (u.randomFloat(u.randomFloat(3))) | |
| runClouds: -> | |
| for a in @clouds() | |
| if a | |
| a.forward 0.3 * (0.1 + (3 + a.cloudNum) / 10) | |
| # | |
| # Sunshine | |
| # | |
| runSunshine: -> | |
| for a in @sunrays() | |
| if a | |
| a.forward 0.5 | |
| @leaveToSpace(a) | |
| @createSunshine() | |
| @reflectSunshineFromClouds() | |
| @encounterEarth() | |
| reflectSunshineFromClouds: -> | |
| for a in @sunrays() | |
| if a | |
| if @clouds().inRadius(a, 1).any() | |
| heading = u.randomFloat2(Math.PI/4, Math.PI*3/4) | |
| if @headingUp a | |
| heading = -heading | |
| a.heading = heading | |
| encounterEarth: -> | |
| for a in @sunrays() | |
| if a.y <= @earthTop | |
| if @albedo * 100 > u.randomInt(100) | |
| @reflectOffHorizontalPlane(a) | |
| else | |
| @transformToHeat(a) | |
| createSunshine: -> | |
| if 0.1 * @sunBrightness > u.randomInt(50) | |
| @agents.create 1, (a) => | |
| a.breed = "sunrays" | |
| a.size = @agentSize | |
| a.color = [255,255,0] | |
| a.heading = @sunlightHeading | |
| a.setXY -24 + u.randomFloat(10), 15 | |
| # | |
| # Main Model Loop | |
| # | |
| step: -> | |
| @runSunshine() | |
| @runClouds() | |
| @runHeat() | |
| @runIR() | |
| @runCO2() | |
| # divName, patchSize, minX, maxX, minY, maxY, isTorus = false, topLeft=[10,10] | |
| # NL Defaults: 13, -16, 16, -16, 16 | |
| APP=new ClimateModel "layers", 12, -24, 24, -15, 15, true #use torus | |
| </script> | |
| <style type="text/css"> | |
| body { font: 13px sans-serif; } | |
| #content { | |
| margin: 0em; | |
| padding: 0em; } | |
| p { font-size: 1.5em; | |
| margin-left: 1.0em } | |
| ul { | |
| list-style-type: none; | |
| margin: 0.3em 0em; | |
| padding-left: 0em; | |
| width: 100%; } | |
| ul li { | |
| display: table-cell; | |
| vertical-align: middle; | |
| margin: 0em; | |
| padding: 0em 0.3em 0em 0.3em; } | |
| #model { | |
| display: table-cell; | |
| margin: 0em 0.5em 0em 0.5em; | |
| width: 600px; } | |
| #layers { | |
| margin: 0em; | |
| padding: 0em; } | |
| #playback-controls { | |
| font-size: 1.1em; | |
| display: inline-block; | |
| width: 100% ; | |
| background-color: #f8f8f8; } | |
| #playback-controls li, button, span { | |
| font-size: 1.1em; } | |
| #controls { | |
| display: table-cell; | |
| vertical-align: top; | |
| margin: 0em 0.5em 0em 0.5em; | |
| padding: 0em; | |
| font-size: 1.0em; } | |
| #controls label, span { | |
| font-size: 1.0em; | |
| vertical-align: top; } | |
| #controls button { | |
| font-size: 1.0em; | |
| vertical-align: middle; } | |
| #controls .output { | |
| font-size: 1.0em; | |
| vertical-align: middle; } | |
| #controls span.digits3 { | |
| font-size: 1.0em; | |
| margin: 0em; | |
| padding: 0em; | |
| float: right; | |
| text-align: left; | |
| width: 2em; } | |
| #controls span.slider-units { | |
| font-size: 90%; | |
| font-style: italic; } | |
| #chart { | |
| display: inline-block; | |
| position: relative; | |
| width: 320px; | |
| height: 250px; | |
| margin: 0.1em 1em 1em 0em; | |
| background-color: #ddf2ff; | |
| border: solid 1px #6CC0FF; | |
| border-radius: 0.25em; } | |
| #chart.plot { | |
| background-color: #f8f8fe; } | |
| text.title { | |
| font-size: 1.6em; } | |
| text.axis { | |
| font-size: 1.1em; } | |
| circle, .line { | |
| fill: none; | |
| stroke: steelblue; | |
| stroke-width: 2px; } | |
| circle { | |
| fill: white; | |
| fill-opacity: 0.2; | |
| cursor: move; } | |
| circle.selected { | |
| fill: #ff7f0e; | |
| stroke: #ff7f0e; } | |
| circle:hover { | |
| fill: #ff7f0e; | |
| stroke: #707f0e; } | |
| circle.selected:hover { | |
| fill: #ff7f0e; | |
| stroke: #ff7f0e; } | |
| </style> | |
| </head> | |
| <body onload="ABM.model.start(); setupControls();"> | |
| <div id="content"> | |
| <div id="model"> | |
| <canvas id="canvas" >Your browser does not support HTML5 Canvas.</canvas> | |
| <div id="layers"></div> | |
| </div> | |
| <div id="controls"> | |
| <div id='chart'></div> | |
| <ul> | |
| <li> | |
| <button id="add-co2-button">Add CO2</button> | |
| <button id="subtract-co2-button">Subtract CO2</button> | |
| </li> | |
| <li> | |
| <span id="co2-output" class="output digits3"></span> | |
| </li> | |
| <li class="output"> | |
| Temperature: <span id="temperature-output"></span> | |
| </li> | |
| </ul> | |
| <ul> | |
| <li> | |
| <label for="albedo-slider">Albedo:</label> | |
| <span class="slider-units">0 <input id="albedo-slider" type="range" min="0" max="1" step="0.01"/> 1</span> | |
| </li> | |
| </ul> | |
| <ul> | |
| <li> | |
| <label for="sun-brightness-slider">Sun Brightness:</label> | |
| <span class="slider-units">0 <input id="sun-brightness-slider" type="range" min="0" max="200" step="1"/> 200</span> | |
| </li> | |
| </ul> | |
| <ul> | |
| <li> | |
| <button id="add-clouds-button">Add Cloud</button> | |
| <button id="subtract-clouds-button">Subtract Cloud</button> | |
| </li> | |
| <li> | |
| <span id="co2-output" class="output digits3"></span> | |
| </li> | |
| </ul> | |
| </div> | |
| <div id="playback-controls"> | |
| <ul> | |
| <li> | |
| <button id="reset-button">Reset</button> | |
| </li> | |
| <li> | |
| <button id="play-button">Play</button> | |
| </li> | |
| <li> | |
| <button id="step-button">Step</button> | |
| </li> | |
| <li> | |
| <button id="stop-button">Stop</button> | |
| </li> | |
| <li> | |
| Model Ticks: <span id="tick-counter"></span> | |
| </li> | |
| </ul> | |
| </div> | |
| </div> | |
| <script> | |
| var addCO2Button = document.getElementById("add-co2-button"), | |
| subtractCO2Button = document.getElementById("subtract-co2-button"), | |
| albedoSlider = document.getElementById("albedo-slider"), | |
| sunBrightnessSlider = document.getElementById("sun-brightness-slider"), | |
| co2Output = document.getElementById("co2-output"), | |
| temperatureOutput = document.getElementById("temperature-output"), | |
| addCloudsButton = document.getElementById("add-clouds-button"), | |
| subtractCloudsButton = document.getElementById("subtract-clouds-button"), | |
| resetButton = document.getElementById("reset-button"), | |
| playButton = document.getElementById("play-button"), | |
| stopButton = document.getElementById("stop-button"), | |
| stepButton = document.getElementById("step-button"), | |
| tickCounter = document.getElementById("tick-counter"), | |
| temperatureFormatter = d3.format("3.1f"), | |
| countFormatter = d3.format("3f"), | |
| ticks = 0, | |
| graph, | |
| graphOptions; | |
| function setupControls() { | |
| albedoSlider.value = ClimateModel.getAlbedo(); | |
| sunBrightnessSlider.value = ClimateModel.getSunBrightness(); | |
| } | |
| addCO2Button.onclick = function() { | |
| ClimateModel.addCO2() | |
| } | |
| subtractCO2Button.onclick = function() { | |
| ClimateModel.subtractCO2() | |
| } | |
| addCloudsButton.onclick = function() { | |
| ClimateModel.addCloud() | |
| } | |
| subtractCloudsButton.onclick = function() { | |
| ClimateModel.subtractCloud() | |
| } | |
| albedoSlider.onchange = function() { | |
| ClimateModel.setAlbedo(+albedoSlider.value) | |
| } | |
| sunBrightnessSlider.onchange = function() { | |
| ClimateModel.setSunBrightness(+sunBrightnessSlider.value) | |
| } | |
| resetButton.onclick = function() { | |
| ClimateModel.setup(); | |
| } | |
| playButton.onclick = function() { | |
| ClimateModel.start(); | |
| } | |
| stopButton.onclick = function() { | |
| ClimateModel.stop(); | |
| } | |
| stepButton.onclick = function() { | |
| ClimateModel.stop(); | |
| ClimateModel.animate(); | |
| updateTickCounter(); | |
| } | |
| function updateTickCounter() { | |
| ticks = ClimateModel.ticks; | |
| tickCounter.textContent = ticks; | |
| } | |
| graphOptions = { | |
| title: "Temperature vs Time (model ticks)", | |
| xlabel: "Time (ticks)", | |
| ylabel: "Temperature", | |
| xmax: 2000, | |
| xmin: 0, | |
| ymax: 40, | |
| ymin: -10, | |
| xTicCount: 4, | |
| xFormatter: "3.3r", | |
| sample: 1 | |
| }; | |
| graph = Lab.grapher.realTimeGraph('#chart', graphOptions); | |
| d3.timer(function(elapsed) { | |
| if (ClimateModel) { | |
| var temperature = ClimateModel.getTemperature(), | |
| co2Count = ClimateModel.getCO2Count(); | |
| temperatureOutput.textContent = temperatureFormatter(temperature); | |
| co2Output.textContent = countFormatter(co2Count); | |
| if (!ClimateModel.animStop) { | |
| graph.add_points([temperature]); | |
| updateTickCounter(); | |
| } | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment