Assignment 7: Simulation in Go
Objectives
- to use basic Go language elements
- to use composition
Introduction
Gavin Fay, Megan Olsen, et al developed a model of the transmission of Tasmanian devil facial tumor disease (DFTD) to investigate how building a road through their habitat might affect the transmission rate. We will update a Go agent-based simulation to facilitate a greatly simplified version of their model.Assignment
The/c/cs427/hw7
and /c/cs427/hw7/Required
directories contain an agent-based simulation implemented in Go. The
current version features Tasmanian devils (Tazzie
) objects
wandering around a hex-grid world following scents to food sources
represented by Food
objects that emit Scent
objects. The Tazzie
objects contain a field infected
that indicates whether they are infected with DFTD; initially one of the
eight Tasmanian devils is infected.
Part 1: Spreading DFTD
The given implementation does not model interactions between Tasmanian devils because theTazzie
struct embeds NoTazzieAction
, which defines a do-nothing
version of AcceptTazzie
. Remove that embedding
(this required changes to both the struct
definition and
the NewTazzie
function) and add a new AcceptTazzie
function with a Tazzie
receiver, Tazzie
argument,
and no return value. Both the receiver and argument should be pointers.
The argument will be the actor in the interaction and the receiver will
be the patient. The new function should set the infected
flag of the patient to true
20% of the time if the
actor is infected.
Once your new function is complete, make the executable (the makefile
in /c/cs427/hw7/Optional
creates an executable called
Sim
by compiling and linking agents.go
,
world.go
, position.go
and main.go
.
Run the executable and watch the simulated Tasmanian devils interact.
Eventually, all the Tasmanian devils will be infected with DFTD.
Part 2 - Automobiles
Next, add automobiles to the simulation. An automobile is similar to
Tasmanian devils and scents in that they all move in the world toward
a goal. Both automobiles and scents are removed from the simulation
when they reach their goals. Automobiles occasionally generate carrion
(Food
agents) much like food generates scents.
Otherwise, automobiles don't interact with other agents. Those
behaviors are implemented in the Mobile
,
Mortal
, Emitter
, and NoActions
and Inert
structs. Define a new struct Automobile
that embeds those structs.
Also, define a String
function with an Automobile
(pointer) receiver that returns the string "A"
to represent
automobiles in the display. Add a NewAutomobile
function
that takes a world (pointer), and two Hex
objects
(non-pointer) for the starting and ending point of a car.
The NewAutomobile
function should declare a local variable
alive
that is initialized to true
and then
an instance of Automobile
with its various embedded
components initialized to contain:
- a pointer to the world;
- the starting position;
- a pointer to the ending position (note that the ending position
is passed as a non-pointer so you will have to use
&
to get a pointer to it); - a function with no arguments and no return value that the
embedded
Mobile
calls when it reaches the goal, where the implementation here forAutomobile
should set thealive
flag tofalse
; - the values 15 and
nil
in the embeddedEmitter
to indicate to spawn a newAgent
on average every 15 steps and to defer the specification of theEmit
function to later; - a pointer to the
alive
local variable for the embeddedMortal
to check; and - embedded
NoActions
andInert
objects.
Automobile
is created, set its Emit
field to a function that takes no arguments and returns the
Agent
returned by NewFood
,
to which you pass the world and the
Automobile
's embedded Hex
(we have to set this now rather than in the initializer because of
the reference to the outer Automobile
that is not in scope
until it has been initialized). Then return a pointer to that
Automobile
.
Finally, add statements to add a new Automobile
to the world
in the NewWorld
function in world.go
.
You can specify any initial point in the world and any destination.
Building and running the executable should now give a simulation
with an automobile agent moving through the world creating roadkill
(food).
Part 3 - Road
Rather than creating oneAutomobile
, we want to create
agents that spawn Automobiles
. We will call those
agents RoadEnd
s. A RoadEnd
is
stationary, spawns (emits) Automobile
agents, never dies,
and doesn't otherwise interact with other agents, so define
RoadEnd
so that it embeds the corresponding structs.
Define a NewRoadEnd
function that takes a pointer to a
World
, and two (non-pointer) Hex
structs
representing the position of the road endpoint and the matching
road endpoint at the other side of the world. Every 25 time steps
(on average), the embedded
Emitter
should create a new Automobile
that starts at the position of the RoadEnd
and travels
to the position of the opposite RoadEnd
.
Add a String
method with a RoadEnd
pointer
receiver that returns an empty string.
Replace the code in NewWorld
(in world.go
)
that adds a single Automobile
to the world so it added
two RoadEnd
s at the middle of the left and right edges of
the world instead. Your simulation should now be complete with
automobiles randomly appearing on the edges of the world and travelling
to the opposide side, randomly generating new food (roadkill) as they
go.
Graduate Requirement
Note the shared behavior ofScent
and
Automobile
: they both start somewhere, move to a goal,
and are removed from the simulation (die) when they reach that goal.
Refactor the simulation so that common behavior is implemented by
a DiesAtGoal
struct that Scent
and
Automobile
both embed.
DiesAtGoal
should embed Mobile
and
Mortal
. The corresponding NewDiesAtGoal
function should declare a bool
local variable
called alive
that is initialized to true
.
It should create a return a pointer to a DiesAtGoal
whose embedded Mobile
is initialized with the world,
the starting position, a pointer to the ending position,
and a functon that takes no arguments, returns nothing, and sets
alive
to false
. The embedded Mortal
should be initialized with a pointer to alive
. Modify
Automobile
and Scent
so they embed
DiesAtGoal
instead of Mobile
and
Mortal
, and make the corresponding changes to
NewScent
and NewAutomobile
. Note that
NewDiesAtGoal
should return a pointer to the new agent,
so make sure that the types match when you embed that agent.
Testing
The public tests require all the types and functions mentioned above to be present in order to compile and run any individual tests. If you want to test before the entire program is complete, you must add dummy definitions for the structs and functions. The structs can embedStationary
, NonEmitter
, Immortal
,
NoActions
, and Inert
.
The String
and NewXXX
functions can return
nil
.
Submissions
Submit whatever source code (.go) files you created or modified (which should beagents.go
and world.go
)
and a makefile that builds the Sim
executable when
make is run with no arguments or with target Sim
(you can use the one in /c/cs427/hw7/Optional
).
Your makefile must assume that
the provided code has been copied to the current directory; do not
hard-code the directory containing that code in your makefile.