10 Getting started with NetLogo
10.0.1 Test-drive: creating a “pond” terrain
Now, let us make a minimum working example of all elements explained above while advancing towards the Pond Trade model prototype. Let us start with implementing a minimum base for the PondTrade model: the pond-like terrain.
For this section, I recommend starting your own NetLogo file (placed at the root directory of this repository) and writing the following steps progressively. Feel free to copy and paste whenever the code fragments become too long. If you run into any problems at the end of each step, you may check the corresponding complete script at the repository’s root.
10.0.1.1 Step 0: Drawing a blue circle
Inside a single procedure, called create-map
, we order (ask
) all patches
to do something using the structure:
to create-map
ask patches
[
<DO_SOMETHING>
]
end
Next, we use the syntax for setting variables (set <VARIABLE> <VALUE>
) inside the procedure to change patches
color to blue:
to create-map
ask patches
[
set pcolor blue
]
end
Notice that both pcolor
and blue
are ‘primitives’, so they are automatically understood by NetLogo. Remember to consult NetLogo’s Dictionary, if in doubt about such primitives. Color primitives such as blue
or their numeric equivalent are shown in ‘Color Swatches’ inside the Tools menu tab.
Since we don’t want all patches to have the same color, we need to build some structure that give different colors to patches depending on their position, so that a blue circle is draw.
Considering how to draw a circle, we need two bits of information: a center and a radius (respectively, O and R in the figure).
First, we must define a center. Because we won’t use this information for anything else, we can declare and set a local variable (i.e., accessable only from its own context), using the syntax let <VARIABLE> <VALUE>
. We define the center patch coordinates as (0,0):
to create-map
ask patches
[
let centralPatch patch 0 0
set pcolor blue
]
end
We can set a radius for our circle using a single numeric value, e.g. 5, expressed in patch widths:
to create-map
ask patches
[
let centralPatch patch 0 0
let minDistOfLandToCenter 5
set pcolor blue
]
end
However, if we are passing a single absolute numeric value to be compared to a distance, the result will be sensitive to how large is our world grid. This is not wise, given that we might want to use this procedure in grids of different dimensions. Imagine that you are interested in changing the dimensions of the grid but want the circle to cover a similar proportion of it (e.g., adjusting the resolution of our map).
We can easily circunvent this code fragility by stepping-up its complexity. One alternative solution is to work with proportions of one of the grid dimensions, e.g. width (world-width
). For the time being, we will set it to be half of half (a quater) of the width, since we have the circle positioned in the middle of the grid.
to create-map
ask patches
[
let centralPatch patch 0 0
let minDistOfLandToCenter round (0.5 * (world-width / 2))
set pcolor blue
]
end
Running this code will still not produce a blue circle. Rather, we are asking for all patches to paint themselve blue, no matter what the values of centralPatch
or minDistOfLandToCenter
.
To differenciate patches to be painted blue, we now use minDistOfLandToCenter
for evaluating a criterium for a ifelse
structure, finding out if a patch is inside or outside our circle. With this, we are ordering patches to paint themselves green or blue depending on if their distance to the center is less than a given value, i.e. minDistOfLandToCenter
.
ifelse (distance centralPatch < minDistOfLandToCenter)
[
set pcolor blue ; water
]
[
set pcolor green ; land
]
Now, the entire code for the create-map
procedure is finally doing what we expected, drawing a blue circle over a green background:
to create-map
ask patches [
; set central patch
let centralPatch patch 0 0
; set minimum distance to center depending on world width
let minDistOfLandToCenter round (0.5 * (world-width / 2))
ifelse (distance centralPatch < minDistOfLandToCenter)
[
set pcolor blue ; water
]
[
set pcolor green ; land
]
]
end
Pond Trade step 0
10.0.1.2 Step 1a: De-composing “magic numbers”
The code we just created has several fixed arbitrary values (the coordinates of the centralPatch
, the values used to calculate minDistOfLandToCenter
). It is good enough for us to draw a particular blue circle, but it is insufficient to draw other types of blue circle. Of course, the code will never be able to draw anything, if we are not programming it to do it. For instance, the colors blue
and green
are also magic numbers, but we are hardly interest in having them as parameters. We must generalize but also compromise, accepting that there will be possibilities that are not covered by our model.
First, is there any case where the patch 0 0
is not the one at the center of the grid? Imagine that you don’t like to have negative coordinates in your model. Go to “Settings” and modify the “location of origin” to be at the corner. Now, test the create-map
procedure:
Not at all what we are looking for! To correct this behavior, we must calculate the center coordinates, depending on the ranges or sizes of the grid width and height, whatever its configuration. Therefore, we must replace 0
with the calculation minimum + range / 2
for both x and y coordinates:
let centralPatch patch (min-pxcor + floor (world-width / 2)) (min-pycor + floor (world-height / 2))
We use the floor
function to obtain the round lower grid position when the range is an odd number. Because this calculation uses only NetLogo primitives, you can test this by printing it in the console in any NetLogo model. It will return the central patch given your grid settings:
observer> show patch (min-pxcor + floor (world-width / 2)) (min-pycor + floor (world-height / 2))
observer: (patch 16 16)
Now, regarding minDistOfLandToCenter
, we could simply bring it as a parameter to be set in the interface instead of hard-coded (e.g. as a slider). This would be a better design, but we still would have a potential problem. Maybe you do not want the grid to be squared (e.g., 33x33), but rectangular (e.g., 100x20) instead. This is what you would get:
Again, not what we are looking for. No matter how strange our map is shaped, we want our pond to be always fully within the grid. To do this, we must modify our calculation of minDistOfLandToCenter
to account for both width and height. One way of doing it is to calculate the radius for both dimensions and then choose the lowest value.
let minXDistOfLandToCenter round (0.5 * world-width / 2) ; minimum distance in X
let minYDistOfLandToCenter round (0.5 * world-height / 2) ; minimum distance in Y
let minDistOfLandToCenter min (list minXDistOfLandToCenter minYDistOfLandToCenter)
To test our solution, we can run the procedure with different extreme grid settings:
100x20 | 20x100 |
---|---|
![]() |
![]() |
Success!
10.0.1.3 Step 1b: Parameterizing
Once you defined a procedure in raw terms and tested that it does what you expect, you probably want to generalize it further. As the aim of modeling is to represent a type of phenomenon, it is a good practice to program all non-dynamic conditions as parameters. In NetLogo, parameters are often those variables that can be set through user input in the interface tab (e.g., slides, selectors, numeric fields).
After the later changes, we still have two magic numbers in our code. Regarding the calculation of centralPatch
and minDistOfLandToCenter
, we used 2
to divide world-width
and world-height
, so that the circle is always draw in the center of the grid. Although it is possible, we will not replace this value with a parameter. As an exercise aside, you can test the outcome of having different numbers instead of 2
.
The other “magic number” is 0.5
, used to represent the relative size of the pond radius, i.e., the half of the half of the smaller dimension. Here we have a good candidate for a parameter. It is reasonable to believe that the size of the pond will be relevant to our model’s results. Mainly, we expect that larger ponds will make trade slower, assuming a fixed number of settlements, evenly distributed around the pond.
In NetLogo, we create a parameter by adding an input element to the interface tab (e.g., slider) and naming it. In this case, we create a parameter called pondSize
that represents the pond radius as the percentage of the smallest dimension, i.e. varing between 0 and 100. We can use it in the code to replace the two instances of 0.5
with (pondSize / 100)
:
let minXDistOfLandToCenter round ((pondSize / 100) * (world-width / 2)) ; minimum distance in X
let minYDistOfLandToCenter round ((pondSize / 100) * (world-height / 2)) ; minimum distance in Y
let minDistOfLandToCenter min (list minXDistOfLandToCenter minYDistOfLandToCenter)
Note that we use percentage, instead of a proportion (from 0 to 1), for no other reason than because it can improve the inteligibility of our model. I recommend using percentages to format this kind of parameters because they are more intuitive for humans and will be more easily understood by colleagues and the general public with no background in computer science or mathematics.
Once you close a version of any piece of code, it is good practice to increase the spacing between the lines or even break down single lines that are particularly complicated. NetLogo language allows much flexibility in this sense: you can add spaces, tabs, line breaks, and commentary between most elements of your code. Also, enclosing parenthesis are not required but may improve readability.
to create-map
ask patches [
; find central patch, depending on the size of dimensions
let centralPatch
patch
; position in X
(
min-pxcor +
floor (world-width / 2)
)
; position in Y
(
min-pycor +
floor (world-height / 2)
)
print(centralPatch) ; print central patch
; find minimun distance of land pathes to the central patch, depending on the size of dimensions
let minXDistOfLandToCenter round ((pondSize / 100) * (world-width / 2)) ; minimum distance in X
let minYDistOfLandToCenter round ((pondSize / 100) * (world-height / 2)) ; minimum distance in Y
let minDistOfLandToCenter min (list minXDistOfLandToCenter minYDistOfLandToCenter)
ifelse (distance centralPatch < minDistOfLandToCenter)
[
set pcolor blue ; water
]
[
set pcolor green ; land
]
]
end
10.0.1.4 Step 1c: Optimasing
We increased significantly the create-map
procedure, but we now have a process that is both flexible and controllable by user input. Yet, we still have a pending issue to solve.
The fact that the centralPatch
and minDistOfLandToCenter
are local variables (let
) and placed inside ask patches [ <ACTIONS> ]
means that we are creating and destroying a different variable once for every patch. We cannot use these variables (plural intended) outside their enclosing brackets and patches hold no memory of their values before or after this particular action. Does anything feel wrong about this?
Besides taking unnecessary computational resources, this design does not generete any errors and, for now, is quite inofensive. However, while it can be easily solved in a short piece of code, it might become harder to find and have odd repercussions over other parts of our model later on.
The solution is to extract the declaration of the local variables, centralPatch
and minDistOfLandToCenter
, from the patches commands. They are now calculated only once at the start of the procedure and then used by every patch:
to create-map
; find central patch, depending on the size of dimensions
let centralPatch
patch
; position in X
(
min-pxcor +
floor (world-width / 2)
)
; position in Y
(
min-pycor +
floor (world-height / 2)
)
print(centralPatch) ; print central patch
; find minimun distance of land pathes to the central patch, depending on the size of dimensions
let minXDistOfLandToCenter round ((pondSize / 100) * (world-width / 2)) ; minimum distance in X
let minYDistOfLandToCenter round ((pondSize / 100) * (world-height / 2)) ; minimum distance in Y
let minDistOfLandToCenter min (list minXDistOfLandToCenter minYDistOfLandToCenter)
ask patches [
ifelse (distance centralPatch < minDistOfLandToCenter)
[
set pcolor blue ; water
]
[
set pcolor green ; land
]
]
end
Pond Trade step 1
10.0.1.5 Step 2a: Pacing comments and line breaks
Since our code for now is rather simple, we can downgrade the extensive commentary and line-breaking on the calculation of centralPatch
.
Commenting and spacing your code is generally a good practice, but do not get carried away! You must assume that your reader has to know something about the programming language and the context of the model. Sometimes comments and line breaks might be just too many and end up defeating the purpuse of improving the code readabiity.
to create-map
let centralPatch patch (min-pxcor + floor (world-width / 2)) (min-pycor + floor (world-height / 2))
; find minimun distance to center
let minXDistOfLandToCenter round ((pondSize / 100) * (world-width / 2)) ; minimum distance in X
let minYDistOfLandToCenter round ((pondSize / 100) * (world-height / 2)) ; minimum distance in Y
let minDistOfLandToCenter min (list minXDistOfLandToCenter minYDistOfLandToCenter)
ask patches [
ifelse (distance centralPatch < minDistOfLandToCenter)
[
set pcolor blue ; water
]
[
set pcolor green ; land
]
]
end
At every step in this tutorial, we will be downgrading most commentaries added in the previous step.
10.0.1.6 Step 2b: Exploring alternative designs
During refactoring, we should always keep in mind that there are always alternative designs that could generate the outcome we seek, some of which might be more readable or optimal.
In this case, we can simplify the calculation of minDistOfLandToCenter
. This new version initializes a local variable halfSmallerDimension
assuming the smaller dimension is the width. Then, it checks that this is the case, and re-write this value if height is actually smaller. Finally, we calculate minDistOfLandToCenter
as a proportion of halfSmallerDimension
.
let halfSmallerDimension (world-width / 2)
if (world-width > world-height) [ set halfSmallerDimension (world-height / 2) ]
let minDistOfLandToCenter round ((pondSize / 100) * halfSmallerDimension)
This version is less redundant, uses two instead of three local variables, and expresses more clearly that the condition is the comparison between the grid width and height.
10.0.1.7 Step 2c: Colors and shades
Last, we replace the text reference for colors with NetLogo’s numerical codes. Using this numeric system allow us to use many shades of any given color. In this case, we are selecting slightly different shades of blue (106) and green (54). You can consult the color codes in “Tools” > “Color Swatches” or in http://ccl.northwestern.edu/netlogo/docs/programming.html#colors:
Pond Trade step 2
Great, now we can draw a blue circle with a bulletproof piece of NetLogo code. Now let us go a few steps faster and aim at a more interesting outcome.
10.0.1.8 Step 3: Adding noise (stochasticity)
Stochasticity is intrinsic in NetLogo. We were already dealing with random processes since step 0, when asking patches to paint themselves. You probably did not realize, but the command ask patches
demands that patches are ordered somehow. Think as if you were told to ask all your friends to have a look at your new model. Well, but who exactly are you going to ask first? NetLogo solves this dilemma automaticaly by randomizing the order of “asking”. As an exercise, you can reduce the velocity of simulation (top of the interface tab), and execute the create-map
procedure. You will observe each patch changing color, one at a time. This is also a nice example of using stochasticity to assure that an aggregated outcome (i.e. blue circle) is not a mere artefact of any particular schedulle of processes (i.e. the order in which patches change colors). Remember, sequences will be different every time we run our simulation, unless we preset the RNG using a specific ‘seed’ (see http://ccl.northwestern.edu/netlogo/docs/dict/random-seed.html).
But is it so important to have a random order? In our script so far, it is completely irrelevant. Our goal was to draw a blue circle; it does not matter which patch assumes the role of land or water first. However, this will became increasingly relevant as we advance in creating a proper agent-based model, because agents’ states DO normally depend on other agents’ states. Following the scenario where you want to show your model to your friends, imagine that your friends would talk to each other after one of them have seen your model and that some of them are more talkative and some more respected. Can you assume that the order in which you present your model would have no effect on the level of publicity and prestige of your model? ‘I don’t think so’, ‘Who knows?!’, ‘I don’t really care about it’? If these thoughts cross your mind while addressing a process in your model, you are probably better off using stochasticity.
A perfect blue circle is great but it is a poor representation of a real water body. Once we implement the dynamics of the model, it will be quite difficult to explore the effect of geography solely by varying the size of the pond. The first step to creating more interesting set-ups is to add noise to the condition used to determine whether a patch is land or water. NetLogo has a family of primitive functions, random
and alike, that can be used to generate random discrete (integer) and continuos (float) values, following different probability distributions (e.g., uniform, normal, exponential).
For each patch, we sample a random continuous number, add it to the minDistOfLandToCenter
, and use as the threshold distance from the center:
let coastThreshold minDistOfLandToCenter + random-float (halfSmallerDimension * coastalNoiseLevel / 100)
ifelse (distance centralPatch < coastThreshold)
[
set pcolor 106 ; blue for water
]
[
set pcolor 54 ; green for land
]
The function random-float <number>
returns a random “float” number greater or equal to 0.0 and lower than <number>
. To strech your learning skills, we are jumping a few minor steps in refactoring by defining a noise that is a portion of halfSmallerDimension
and controlable through the parameter coastalNoiseLevel
, exposed in the interface.
to create-map
let centralPatch patch (min-pxcor + floor (world-width / 2)) (min-pycor + floor (world-height / 2))
let halfSmallerDimension (world-width / 2)
if (world-width > world-height) [ set halfSmallerDimension (world-height / 2) ]
let minDistOfLandToCenter round ((pondSize / 100) * halfSmallerDimension)
ask patches
[
; add noise to coast line
let coastThreshold minDistOfLandToCenter + random-float (halfSmallerDimension * coastalNoiseLevel / 100)
ifelse (distance centralPatch < coastThreshold)
[
set pcolor 106 ; blue for water
]
[
set pcolor 54 ; green for land
]
]
end
We now can generate strange “spray ponds”. More importantly, we made the generation process controllable through two parameters that are easily understandable. Play with the parameters and meditate on their effects on the shape of the pond.
Pond Trade step 3
10.0.1.9 Step 4b: design alternatives
Because a “spray pond” is not exactly what we want, let us implement another alternative to add stochasticity. NetLogo offers the random-normal
primitive, which samples a random “float-point number” given the mean and standard deviation of a normal distribution, using the structure random-normal <MEAN> <STD.DEV>
(https://ccl.northwestern.edu/netlogo/docs/dictionary.html#random-reporters). In this context, we can re-use the paramater coastalNoiseLevel
for the latter and consider minDistOfLandToCenter
as the mean.
Comment-out the line with random-float
and add the following:
; let coastThreshold minDistOfLandToCenter + random-float (halfSmallerDimension * coastalNoiseLevel / 100)
let coastThreshold random-normal minDistOfLandToCenter (halfSmallerDimension * coastalNoiseLevel / 100)
We can now experiment running create-map
in three different ways: without noise, with uniform-distributed noise, and with normal-distributed noise. We can run each by commenting-out the line corresponding to the other two. For example, to come back to the no noise option:
; no noise
let coastThreshold minDistOfLandToCenter
; uniform-distributed noise
; let coastThreshold minDistOfLandToCenter + random-float (halfSmallerDimension * coastalNoiseLevel / 100)
; normal-distributed noise
let coastThreshold random-normal minDistOfLandToCenter (halfSmallerDimension * coastalNoiseLevel / 100)
no noise | ![]() |
uniform | ![]() |
normal | ![]() |
But at this point, can we decide which option we want going forward?
10.0.1.10 Step 4c: keeping design alternatives
As mentioned, stochasticity may be the only honest way of defining values that are unknown or undefined in our conceptual model, but still need specification in the model implementation. Another useful, still honest approach is to consolidate alternative solutions and expose the criterium for deciding among them as a special type of parameter. Often, these will be Booleans (true
or false
), which can be set with “switchers” in NetLogo interface, or String (declarative text, e.g. "option A"
, "option B"
, "option C"
), which can be selected through “choosers” or dropdown menus.
Create a chooser by clicking at “Add” and then “chooser” in the NetLogo Interface tab. Give it the name noiseType
and write down a name for each alternative as String values:
The configuration of a “chooser” for noiseType
Then, we use this new parameter in if
structures containing the code specific for each alternative:
; no noise stands as the default alternative
let noiseRange (halfSmallerDimension * coastalNoiseLevel / 100)
ask patches
[
if (noiseType = "uniform")
[
; adds a random amount from a uniform distribution with mean minDistOfLandToCenter
set noiseRange (random-float noiseRange) - (noiseRange / 2)
set coastThreshold minDistOfLandToCenter + noiseRange
]
if (noiseType = "normal")
[
; adds a random amount from a normal distribution with mean minDistOfLandToCenter
set coastThreshold random-normal minDistOfLandToCenter noiseRange
]
...
]
With this, we can now run create-map
with any of the alternative designs without having to comment-out/in the code every time.
10.0.1.11 Step 4d: patch neighborhood
Despite having all the alternative modes of stochasticity, our ponds are still not quite what we need. This kind of terrain might be valid to represent something like a swamp, but not a distinctive water body. There is no coastline, which makes PondTrade more interesting.
One generic, but very useful technique in distributed computation is “smoothing”. More precisily, smoothing refers to the approximation of each point in a variable to the average of a sample of points, often taken from within the neighbourhood of the point.
In a two-dimensional grid such as in NetLogo, we can use the values in the immediate patch neighbourhood of each patch to adjust its value. In our present case, we want to switch the color of a patch, depending on the color of the adjacent patches. We can antecipate, however, that we will need to expose another paramater, one that can express how many neighbors (out of eight) of a type are enough to activate the change. We will call it coastLineSmoothThreshold
. Notice that this step moves data in the opposite direction of our last steps, reducing stochasticity. Yet, it does not bringing us back to the original circle. Therefore, we want to smooth after adding noise.
Remembering our mandate of modularity, we want to avoid adding the code for this operation directly in create-map
. Rather, we can implement it in a cleaner way by enclosing it in a new procedure:
to smooth-coast-line
ask patches
[
ifelse (pcolor = 106)
[
; water patch
if (count neighbors with [pcolor = 54] >= coastLineSmoothThreshold)
[
; water patch has a certain number of land neighbors
set pcolor 54 ; converted to land
]
]
[
; land patch
if (count neighbors with [pcolor = 106] >= coastLineSmoothThreshold)
[
; land patch has a certain number of water neighbors
set pcolor 106 ; converted to water
]
]
]
end
We can then used it by calling smooth-coast-line
after create-map
, either through the console or, better, by adding a button in the interface. Testing it for each noiseType
:
no noise | ![]() |
uniform | ![]() |
normal | ![]() |
Notice that there is no effect visible on the no noise option, but we significantly modify the outcome of the others.
10.0.1.12 Step 4e: iterative structures
Try pressing the new smooth-coast-line
button more than once after create-map
. The coastline will become smoother and smoother, until it becomes stable. This behaviour indicates that we have a good opportunity for implementing and iterative structure. NetLogo has a easy option for this using the follow structure: repeat <NUMBER OF ITERATIONS> [ <COMMANDS> ]
, requiring that we expose another parameter specifying the number of iterations, which we will call smoothIterations
.
to smooth-coast-line
; smooth coast line
repeat smoothIterations
[
ask patches
[
ifelse (pcolor = 106)
[
; water patch
if (count neighbors with [pcolor = 54] >= coastLineSmoothThreshold)
[
; water patch has a certain number of land neighbors
set pcolor 54 ; converted to land
]
]
[
; land patch
if (count neighbors with [pcolor = 106] >= coastLineSmoothThreshold)
[
; land patch has a certain number of water neighbors
set pcolor 106 ; converted to water
]
]
]
]
end
10.0.1.13 Step 4f: printing event messages
Since we are expanding considerably the complexity of our code, this is a good moment to consider printing messages for us to know what is happening in the background after pressing the buttons in the interface. This will be valuable specially when the code is encountering an error or outputing an unexpected result (i.e. debugging).
to create-map
print "Creating map..."
; erase previous data
clear-all
let centralPatch patch (min-pxcor + (floor world-width / 2)) (min-pycor + (floor world-height / 2))
let halfSmallerDimension (world-width / 2)
if (world-width > world-height) [ set halfSmallerDimension (world-height / 2) ]
let minDistOfLandToCenter round ((pondSize / 100) * halfSmallerDimension)
let coastThreshold minDistOfLandToCenter ; defaults to the basic value
;; add noise to coast line
; set general noise range depending on UI's coastalNoiseLevel and the size of world
let noiseRange (halfSmallerDimension * coastalNoiseLevel / 100)
print "Assigning initial patch types..."
ask patches
[
; noiseType is specified with the chooser in the UI
if (noiseType = "uniform")
[
; adds a random amount from a uniform distribution with mean minDistOfLandToCenter
set noiseRange (random-float noiseRange) - (noiseRange / 2)
set coastThreshold minDistOfLandToCenter + noiseRange
]
if (noiseType = "normal")
[
; adds a random amount from a normal distribution with mean minDistOfLandToCenter
set coastThreshold random-normal minDistOfLandToCenter noiseRange
]
ifelse (distance centralPatch < coastThreshold)
[
set pcolor 106 ; blue for water
]
[
set pcolor 54 ; green for land
]
]
print "done."
end
to smooth-coast-line
print "Smoothing..."
; smooth coast line
repeat smoothIterations
[
ask patches
[
ifelse (pcolor = 106)
[
; water patch
if (count neighbors with [pcolor = 54] >= coastLineSmoothThreshold)
[
; water patch has a certain number of land neighbors
set pcolor 54 ; converted to land
]
]
[
; land patch
if (count neighbors with [pcolor = 106] >= coastLineSmoothThreshold)
[
; land patch has a certain number of water neighbors
set pcolor 106 ; converted to water
]
]
]
]
print "done."
end
We just consolidated a version of our script that is able to create interesting (and more realistic) waterbody shapes with stochasticity, while being able to vary the output with a set of five parameters clearly displayed and no grave magic number unexposed.
Pond Trade step 4
10.0.1.14 Step 5: refactoring (again)
We reached our initial objective, to have a realistic procedurally-generated terrain upon which we can build PondTrade. As before, we should now stop ourselves and think about refactoring, again.
Here is a summary of the improvements we can make:
- Control the RNG seed and expose it in the interface
- Move the initialisation of
coastThreshold
andnoiseRange
beforeask patches
- Declare a new patch Boolean variable called
isLand
and use it to replace color in the procedures for creating terrains - Define a new procedure
paint-patches
dedicated only to set patch colors based onisLand
- Call
smooth-coast-line
andpaint-patches
at the end ofcreate-map
- In the evaluation of
coastLineSmoothThreshold
used insmooth-coast-line
, consider it in relation to the actual number of neighbors instead of as an absolute number (to avoid having isolated water bodies adjacent to the world edges, where there are less than 8 neighbors) - Rearrange the interface elements to set apart the parameters we will be using for terrain generation
patches-own [ isLand ]
to setup
clear-all
; set the random seed so we can reproduce the same experiment
random-seed seed
create-map
end
to create-map
let centralPatch patch (min-pxcor + (floor world-width / 2)) (min-pycor + (floor world-height / 2))
let halfSmallerDimension (world-width / 2)
if (world-width > world-height) [ set halfSmallerDimension (world-height / 2) ]
let minDistOfLandToCenter round ((pondSize / 100) * halfSmallerDimension)
let coastThreshold minDistOfLandToCenter ; defaults to the basic value
;; add noise to coast line
; set general noise range depending on UI's coastalNoiseLevel and the size of world
let noiseRange (halfSmallerDimension * coastalNoiseLevel / 100)
ask patches
[
; noiseType is specified with the chooser in the UI
if (noiseType = "uniform")
[
; adds a random amount from a uniform distribution with mean minDistOfLandToCenter
set noiseRange (random-float noiseRange) - (noiseRange / 2)
set coastThreshold minDistOfLandToCenter + noiseRange
]
if (noiseType = "normal")
[
; adds a random amount from a normal distribution with mean minDistOfLandToCenter
set coastThreshold random-normal minDistOfLandToCenter (halfSmallerDimension * coastalNoiseLevel / 100)
]
ifelse (distance centralPatch < coastThreshold)
[
set isLand false
]
[
set isLand true
]
]
smooth-coast-line
paint-patches
end
to smooth-coast-line
; smooth coast line
repeat smoothIterations
[
ask patches
[
ifelse (isLand = false)
[
; water patch
; consider ratios instead of absolute numbers to avoid having isolated water bodies adjacent to the world limits (less than 8 neighbors)
if (count neighbors with [isLand = true] / count neighbors >= coastLineSmoothThreshold / 8)
[
; water patch has a certain number of land neighbors
set isLand true ; converted to land
]
]
[
; land patch
if (count neighbors with [isLand = false] / count neighbors >= coastLineSmoothThreshold / 8)
[
; land patch has a certain number of water neighbors
set isLand false ; converted to water
]
]
]
]
end
to paint-patches
ask patches
[
ifelse (isLand = false)
[ set pcolor 106 ] ; blue for water
[ set pcolor 54 ] ; green for land
]
end
Pond Trade step 5