agentscript
Version:
AgentScript Model in Model/View architecture
401 lines (324 loc) • 15 kB
HTML
<html lang="en">
<head>
<title>Snippets</title>
<link rel="stylesheet" type="text/css" href="https://backspaces.github.io/layouts/root.css">
<link rel="stylesheet" type="text/css" href="https://backspaces.github.io/layouts/layouts.css">
<script type="module" src="https://backspaces.github.io/layouts/layouts.js"></script>
<style>
sidebar-layout {
max-inline-size: 140ch;
}
.note {
font-size: 0.85em;
background: #ffe8cf;
padding: 12px 20px;
line-height: 1.4em;
/* margin: 40px 20px; */
}
.pink {
background: #fffbfb;
}
code {
background: #d9d1ff;
}
button {
font-size: 18px;
height: 40px;
background: skyblue;
padding: 10px 14px;
border-width: 0;
margin: 0;
}
button.forever {
background: orange;
}
.snippet {
background: white;
}
/* ==== These are for the gradientColorMap or not used (commented out) ==== */
/* flex container */
#colorMapGrid {
display: flex;
height: 45px;
border-width: 0;
margin: 0;
padding: 0;
/* background: white;
flex-direction: row; */
}
/* flex items */
#colorMapGrid * {
flex-grow: 1;
border-width: 0;
margin: 0;
padding: 0;
/* margin: 1px;
border-color: green;
border-width: 2;
padding: 3; */
}
pre {
margin: 0;
padding: 0;
white-space: pre;
/* background: white; */
}
</style>
</head>
<body class="pink">
<script>
function runSnippet(id) {
let text = document.getElementById(id).innerText;
eval(text)
}
let modelIsRunning = false
function runForever(id) {
if (id.includes('pheromone')) setPatchesColor()
let text = modelIsRunning ? '' : document.getElementById(id).innerText;
model.step = function () { eval(text) }
modelIsRunning = !modelIsRunning
const btnId = id + 'Btn'
let btn = document.getElementById(btnId)
btn.innerText = modelIsRunning ? 'stop running' : 'run forever'
console.log(btnId, btn);
}
function resetAll() {
// model.reset()
model.ticks = 0
model.turtles.length = 0
model.links.length = 0
view.drawOptions.patchesColor = 'black'
model.patches.ask(p => p.pheromone = 0)
}
</script>
<!-- <sidebar-layout side="left" sideWidth="25ch" contentMin="40%" space="2ch"> -->
<sidebar-layout side="left" sideWidth="20%" contentMin="40%" space="2ch" style="padding-top:0;">
<!-- ==== Here are the code snippets, in the left sidebar ==== -->
<box-layout borderWidth="0px" class="pink">
<h2>What is Agentscript?</h2>
<p>Agentscript is an open source javascript library for writing agent-based models. It is inspired by a programming language called Netlogo.</p>
<h2>How does it work?</h2>
<p>In AgentScript, you fill a world with three ingredients: turtles, patches, and links.</p>
<p>Your program describes the behavior of each of these actors and how they interact
with each other. Let's see an example.</p>
<h2>Turtles</h2>
<p> First things first, let's make a turtle: </p>
<box-layout id="createTurtle" contenteditable: "true" class="snippet">
model.turtles.create(1)
</box-layout>
<button type="button" onclick="runSnippet('createTurtle')">run once</button>
<p> Click the "run once" button to execute that code once.</p>
<p> You'll see a turtle appear in the center of the world. It has the shape of a chevron.</p>
<p class="note">This tutorial assumes you have a little familiarity with javascript.
If you have never seen javascript before, or if you want a refresher, try out this interactive tutorial: <a href="https://jgthms.com/javascript-in-14-minutes/">Javascript in 14 Minutes</a>.
</p>
<p>Now that we have a turtle, we can ask it do something, like move forward:</p>
<box-layout id="turtleForward" contenteditable="true" class="snippet">
<pre>model.turtles.ask(turtle => {
turtle.forward(1)
})</pre>
</box-layout>
<button type="button" onclick="runSnippet('turtleForward')">run once</button>
<p class="note">If you are familiar with javascript, it may help to know that
<code>turtles.ask()</code> has the same meaning as <code>turtles.forEach()</code>.
</p>
<p>If you keep telling a turtle to move forward, it will eventually hit
the edge of the world and wrap around to the other side!</p>
<p>We can also ask turtles to rotate:</p>
<box-layout div id="turtleRotate" contenteditable="true" class="snippet">
<pre>model.turtles.ask(turtle => {
turtle.rotate(45)
})</pre>
</box-layout>
<button type="button" onclick="runSnippet('turtleRotate')">run once</button>
<p>Or both — move forward and rotate:</p>
<box-layout id="turtleBoth" contenteditable="true" class="snippet">
<pre>model.turtles.ask(turtle => {
turtle.forward(1)
turtle.rotate(25)
})</pre>
</box-layout>
<button type="button" onclick="runSnippet('turtleBoth')">run once</button>
<p>Try changing one of the numbers in the above code so that the turtle
makes smaller steps, or bigger turns.</p>
<p>Instead of working one step at a time, things get really interesting
when you run a behavior over and over again.</p>
<p>I added a "run forever" button to the code snippet above. Try it out!</p>
<box-layout id="turtleBoth1" contenteditable="true" class="snippet">
<pre>model.turtles.ask(turtle => {
turtle.forward(1)
turtle.rotate(25)
})</pre>
</box-layout>
<button type="button" onclick="runSnippet('turtleBoth1')">run once</button>
<button id="turtleBoth1Btn" class="forever" type="button" onclick="runForever('turtleBoth1')">
run forever
</button>
<p>How about a little randomness? Here's the same code, as before, but
this time using <code>util.randomInt()</code> to pick a random angle
to turn to the right and to the left.
The result is turtles that wander randomly around the world:</p>
<box-layout id="turtleRandom" contenteditable="true" class="snippet">
<pre>model.turtles.ask(turtle => {
turtle.forward(1)
turtle.right(util.randomInt(25))
turtle.left(util.randomInt(25))
})</pre>
</box-layout>
<button type="button" onclick="runSnippet('turtleRandom')">run once</button>
<button id="turtleRandomBtn" class="forever" type="button" onclick="runForever('turtleRandom')">
run forever
</button>
<p> If you haven't already, try adding a bunch more turtles! </p>
<box-layout id="create10Turtles" contenteditable="true" class="snippet">
model.turtles.create(10)
</box-layout>
<button type="button" onclick="runSnippet('create10Turtles')">run once</button>
<h2>Links</h2>
<p>Two turtles can create links between themselves.
This is useful for creating a network, such as streets
for turtles to drive on. Here is a simple example:
<a href="https://code.agentscript.org/views2mv/linktravel.html">
Link Travel
</a>
</p>
<p>Try this, making sure you have lots of turtles. You can use the create(10) above.</p>
<box-layout id="createLinks" contenteditable="true" class="snippet">
<pre>const turtle1 = model.turtles.oneOf()
const turtle2 =
model.turtles.otherOneOf(turtle1)
turtle1.forward(1)
turtle2.forward(1)
const link =
model.links.createOne(turtle1, turtle2)</pre>
</box-layout>
<button type="button" onclick="runSnippet('createLinks')">run once</button>
<h2>Patches</h2>
<p>The world where the turtles live and move is divided up into a bunch of small squares called patches.</p>
<p>A turtle always knows what patch it's on — this is stored in <code>turtle.patch</code>.</p>
<p>Right now, all the patches are black, which is why the world behind the turtles looks like a big black square.</p>
<p>Let's pretend like our turtles are ants, and they are dropping chemical pheromones everywhere they walk (this is in fact how ants communicate!)</p>
<box-layout id="pheromone" contenteditable="true" class="snippet">
<pre>model.turtles.ask(turtle => {
turtle.forward(1)
turtle.right(util.randomInt(25))
turtle.left(util.randomInt(25))
turtle.patch.pheromone += 10
})</pre>
</box-layout>
<button type="button" onclick="runSnippet('pheromone')">run once</button>
<button id="pheromoneBtn" class="forever" type="button" onclick=" runForever('pheromone')">
run forever
</button>
<p>The landscape is now filling up with pheromones!</p>
<p>I set up the patches ahead of time so that their color depends on how much pheromone they have. This is the color scale I'm using:</p>
<box-layout id="colorMap" contenteditable="true" class="snippet">
<pre>myColorMap = ColorMap.gradientColorMap(
8, ['black', 'purple', 'yellow']
)</pre>
</box-layout>
<button type="button" onclick="runSnippet('colorMap'); setColoMap(myColorMap)">run once</button>
<box-layout id="colorMapGrid"> </box-layout>
<p>Try changing the number or names of the colors in the code block above.</p>
<p>The size and number of patches that make up the world can also be configured — we'll look at how to edit that later.</p>
<p>Now let's make the pheromone evaporate over time:</p>
<box-layout id="pheromoneEvap" contenteditable="true" class="snippet">
<pre>model.turtles.ask(turtle => {
turtle.forward(1)
turtle.rotate(util.randomCentered(50))
turtle.patch.pheromone += 10
})
model.patches.ask(patch => {
patch.pheromone *= 0.99
})</pre>
</box-layout>
<button type="button" onclick="runSnippet('pheromoneEvap')">run once</button>
<button id="pheromoneEvapBtn" class="forever" type="button" onclick="runForever('pheromoneEvap')">
run forever
</button>
<!-- <box-layout borderWidth="0px" class="pink">
<p>Now lets return to the <a href="/docs/tutorial-3 - AgentScript.html">AgentScript tutorial</a></p>
</box-layout> -->
<!-- <box-layout borderWidth="0px" class="pink">
<p>Now lets return to the <a href="/docs/tutorial-AgentScript.html">AgentScript tutorial</a></p>
</box-layout> -->
</box-layout>
<!-- ==== Here is the model in the right side of the sidebar-layout ==== -->
<box-layout borderWidth="1px" class="pink">
<box-layout borderWidth="0px" style="position: sticky; top: 40px;" class="pink">
<div id="modelDiv"></div>
<button type="button" onclick="resetAll()">reset</button>
<button type="button" onclick="model.turtles.create(10)">10 turtles</button>
</box-layout>
</box-layout>
</sidebar-layout>
<script type="module">
import * as util from 'https://code.agentscript.org/src/utils.js'
import TwoDraw from 'https://code.agentscript.org/src/TwoDraw.js'
import ColorMap from 'https://code.agentscript.org/src/ColorMap.js'
import Animator from 'https://code.agentscript.org/src/Animator.js'
import Model from 'https://code.agentscript.org/src/Model.js'
import World from 'https://code.agentscript.org/src/World.js'
const model = new Model(World.defaultOptions(10))
// await model.startup()
// model.setup()
model.patches.ask(p => { p.pheromone = 0 })
let myColorMap = ColorMap.gradientColorMap(
8, ['black', 'purple', 'yellow']
)
// function resetAll() {
// // model.reset()
// model.ticks = 0
// model.turtles.length = 0
// model.links.length = 0
// view.drawOptions.patchesColor = 'black'
// model.patches.ask(p => p.pheromone = 0)
// }
function setPatchesColor() {
view.drawOptions.patchesColor =
(p) => myColorMap.scaleColor(p.pheromone, 0, 100)
}
function setColoMap(colorMap = myColorMap) {
myColorMap = colorMap
colorMapGrid()
}
function colorMapGrid() {
const element = document.getElementById('colorMapGrid')
const children = element.children
// remove current div's
while (element.firstChild) {
element.removeChild(element.firstChild);
}
// add new div's
const length = myColorMap.length
util.repeat(length, (i) => {
const div = document.createElement("div")
const uInts = myColorMap[i]
const color = `rgba(${uInts[0]},${uInts[1]},${uInts[2]},${uInts[3]})`
div.style.backgroundColor = color
element.append(div)
})
}
colorMapGrid() // initialize!
const drawOptions = {
div: 'modelDiv',
patchSize: 20,
drawOptions: {
turtlesSize: 2,
patchesColor: 'black'
}
}
let view = new TwoDraw(model, drawOptions)
const animator = new Animator(
() => {
model.step()
view.draw()
},
-1, // run forever
30 // 30 fps
)
util.toWindow({ util, model, view, ColorMap, resetAll, setPatchesColor, setColoMap })
</script>
</body>
</html>