diff --git a/src/Persefone.jl b/src/Persefone.jl index aae3d8d88a8f49c65d5170128eae1d9bc75388e0..37390f4b10628bec68e67a757899c7788af0160d 100644 --- a/src/Persefone.jl +++ b/src/Persefone.jl @@ -129,7 +129,7 @@ include("nature/ecologicaldata.jl") # end #include("nature/species/skylark.jl") include("nature/species/wolpertinger.jl") -#include("nature/species/wyvern.jl") +include("nature/species/wyvern.jl") include("core/simulation.jl") #this must be last diff --git a/src/core/simulation.jl b/src/core/simulation.jl index 7ec91bc95c3ad951f99d329745fbe8f35d2ec15d..c9ed15a2f3ea73ce6928aeaec588e506f9307a28 100644 --- a/src/core/simulation.jl +++ b/src/core/simulation.jl @@ -188,7 +188,7 @@ function finalise!(model::SimulationModel) with_logger(model.logger) do @info "Simulated $(model.date-@param(core.startdate))." @info "Simulation run completed at $(Dates.now()),\nwrote output to $(@param(core.outdir))." - #@param(core.visualise) && visualiseoutput(model) #FIXME + @param(core.visualise) && visualiseoutput(model) model end end diff --git a/src/nature/macros.jl b/src/nature/macros.jl index fd8242f50b5cf0ea9da4ba98653d15b948521d71..9d768e6345b9d0596ab03b0f900d1bc2dedd77bd 100644 --- a/src/nature/macros.jl +++ b/src/nature/macros.jl @@ -210,6 +210,16 @@ macro kill(args...) :(kill!($(esc(:self)), $(esc(:model)), $(map(esc, args)...)) && return) end +""" + @killother + +Kill another animal. This is a thin wrapper around [`kill!`](@ref), and passes on +any arguments. This can only be used nested within [`@phase`](@ref). +""" +macro killother(animal, args...) + :(kill!($(esc(animal)), $(esc(:model)), $(map(esc, args)...)) && return) +end + """ @reproduce @@ -310,14 +320,25 @@ macro cropheight() end """ - @distanceto(habitat) + @directionto -Calculate the distance to the closest habitat of the specified type or descriptor. -This is a utility wrapper that can only be used nested within [`@phase`](@ref) +Calculate the direction to an animal or the closest habitat of the specified type or +descriptor. This is a utility wrapper that can only be used nested within [`@phase`](@ref) +or [`@habitat`](@ref). +""" +macro directionto(target) + :(directionto($(esc(:pos)), $(esc(:model)), $(esc(target)))) +end + +""" + @distanceto + +Calculate the distance to an animal or the closest habitat of the specified type or +descriptor. This is a utility wrapper that can only be used nested within [`@phase`](@ref) or [`@habitat`](@ref). """ -macro distanceto(habitat) - :(distanceto($(esc(:pos)), $(esc(:model)), $(esc(habitat)))) +macro distanceto(target) + :(distanceto($(esc(:pos)), $(esc(:model)), $(esc(target)))) end """ @@ -344,10 +365,20 @@ macro randompixel(args...) end """ - @neighbours(radius=0) + @randomdirection(range=1) + +Return a random direction tuple that can be passed to [`@walk`](@ref). +This is a utility wrapper that can only be used nested within [`@phase`](@ref). +""" +macro randomdirection(args...) + :(randomdirection($(esc(:model)), $(map(esc, args)...))) +end + +""" + @neighbours(radius=0, conspecifics=true) -Return an iterator over all conspecific animals in the given radius around this animal, -excluding itself. This can only be used nested within [`@phase`](@ref). +Return an iterator over all (by default conspecific) animals in the given radius around +this animal, excluding itself. This can only be used nested within [`@phase`](@ref). """ macro neighbours(args...) :(neighbours($(esc(:self)), $(esc(:model)), $(map(esc, args)...))) @@ -366,16 +397,16 @@ macro move(position) end """ - @walk(direction) + @walk(direction, speed) Walk the animal in a given direction, which is specified by a tuple of coordinates relative to the animal's current position (i.e. `(2, -3)` increments the X coordinate by 2 and decrements the Y coordinate by 3.) This is a utility wrapper that can only be used nested within [`@phase`](@ref). """ -macro walk(direction) +macro walk(args...) #XXX add `ifempty` keyword? - :(walk!($(esc(:self)), $(esc(:model)), $(esc(direction)))) + :(walk!($(esc(:self)), $(esc(:model)), $(map(esc, args)...))) end """ diff --git a/src/nature/populations.jl b/src/nature/populations.jl index 98cece638caffe4d7bcc150739e862dcc2128377..44fa2b7ceab11c6922aec161d2c6b921a2f6a093 100644 --- a/src/nature/populations.jl +++ b/src/nature/populations.jl @@ -133,7 +133,7 @@ function reproduce!(animal::Animal, model::SimulationModel, end """ - kill(animal, model, probability=1.0, cause="") + kill!(animal, model, probability=1.0, cause="") Kill this animal, optionally with a given percentage probability. Returns true if the animal dies, false if not. @@ -214,13 +214,35 @@ function nearby_animals(pos::Tuple{Int64,Int64}, model::SimulationModel; end """ - neighbours(animal, model, radius=0) + neighbours(animal, model, radius=0, conspecifics=true) -Return a list of conspecific animals in the given radius around this animal, excluding itself. +Return a list of animals in the given radius around this animal, excluding itself. By default, +only return conspecific animals. """ -function neighbours(animal::Animal, model::SimulationModel, radius::Int64=0) +function neighbours(animal::Animal, model::SimulationModel, radius::Int64=0, conspecifics=true) filter(a -> a.id != animal.id, - nearby_animals(animal.pos, model, radius=radius, species=speciesof(animal))) + nearby_animals(animal.pos, model, radius = radius, + species = conspecifics ? speciesof(animal) : "")) +end + +""" + directionto(pos, model, animal) + +Calculate the direction from the given position to the animal. +""" +function directionto(pos::Tuple{Int64,Int64}, model::SimulationModel, animal::Animal) + # have to use a coordinate as first argument rather than an animal because of @directionto + animal.pos - pos +end + +""" + distanceto(pos, model, animal) + +Calculate the distance from the given position to the animal. +""" +function distanceto(pos::Tuple{Int64,Int64}, model::SimulationModel, animal::Animal) + # have to use a coordinate as first argument rather than an animal because of @distanceto + maximum(abs.(animal.pos - pos)) end """ @@ -233,7 +255,7 @@ function followanimal!(follower::Animal, leader::Animal, model::SimulationModel, #TODO test function spacing = Tuple(@rand(-distance:distance, 2)) targetposition = safebounds(spacing .+ leader.pos, model) - move_agent!(follower, targetposition, model) + move!(follower, model, targetposition) end """ @@ -282,16 +304,23 @@ function walk!(animal::Animal, model::SimulationModel, direction::String) end """ - walk!(animal, model, direction) + walk!(animal, model, direction, speed=-1) Let the animal move in the given direction, where the direction is defined by an (x, y) tuple to specify the shift in coordinates. +If speed >= 0, walk no more than the given number of cells. """ -function walk!(animal::Animal, model::SimulationModel, direction::Tuple{Int64,Int64}) - move!(animal, model, animal.pos .+ direction) +function walk!(animal::Animal, model::SimulationModel, direction::Tuple{Int64,Int64}, speed::Int64=-1) + #TODO add a habitat descriptor? + if speed >= 0 + direction[1] > speed && (direction[1] = speed) + direction[2] > speed && (direction[2] = speed) + direction[1] < -speed && (direction[1] = -speed) + direction[2] < -speed && (direction[2] = -speed) + end + newpos = animal.pos .+ direction + move!(animal, model, newpos) end - - ##TODO add random walk with habitat descriptor ##TODO add walktoward or similar function (incl. pathfinding?) diff --git a/src/world/landscape.jl b/src/world/landscape.jl index bd9af0faf3d1265e0c0c126e0401057ec163f442..1a9d09308c6ac706263020b688553a377481ccf7 100644 --- a/src/world/landscape.jl +++ b/src/world/landscape.jl @@ -32,7 +32,7 @@ A data structure to define a landscape event, giving its type, spatial extent, and duration. """ mutable struct FarmEvent - type::EventType + etype::EventType pixels::Vector{Tuple{Int64,Int64}} duration::Int64 end @@ -80,7 +80,7 @@ function updateevents!(model::SimulationModel) if event.duration <= 0 push!(expiredevents, e) for p in event.pixels - i = findnext(x -> x == event.type, model.landscape[p...].events, 1) + i = findnext(x -> x == event.etype, model.landscape[p...].events, 1) deleteat!(model.landscape[p...].events, i) end end @@ -116,20 +116,23 @@ end Return the farm plot at this position, or nothing if there is none (utility wrapper). """ function farmplot(pos::Tuple{Int64,Int64}, model::SimulationModel) - ismissing(model.landscape[pos...].fieldid) ? nothing : - model[model.landscape[pos...].fieldid] + if ismissing(model.landscape[pos...].fieldid) + nothing + else + model[model.landscape[pos...].fieldid] + end end - """ - distanceto(pos, model, habitatdescriptor) + directionto(pos, model, habitatdescriptor) -Calculate the distance from the given location to the closest location matching the -habitat descriptor function. Caution: can be computationally expensive! +Calculate the direction from the given location to the closest location matching the +habitat descriptor function. Returns a coordinate tuple (target - position), or nothing +if no matching habitat is found. Caution: can be computationally expensive! """ -function distanceto(pos::Tuple{Int64,Int64}, model::SimulationModel, habitatdescriptor::Function) - #XXX allow testing for multiple habitat types? - (habitatdescriptor(pos, model)) && (return 0) +function directionto(pos::Tuple{Int64,Int64}, model::SimulationModel, habitatdescriptor::Function) + #TODO test + (habitatdescriptor(pos, model)) && (return (0,0)) dist = 1 width, height = size(model.landscape) while dist <= width || dist <= height @@ -139,8 +142,8 @@ function distanceto(pos::Tuple{Int64,Int64}, model::SimulationModel, habitatdesc minx = maximum((pos[1]-dist, 1)) maxx = minimum((pos[1]+dist, width)) for x in minx:maxx - (y1 > 0 && habitatdescriptor((x,y1), model)) && (return dist) - (y2 <= height && habitatdescriptor((x,y2), model)) && (return dist) + (y1 > 0 && habitatdescriptor((x,y1), model)) && (return (x, y1)) + (y2 <= height && habitatdescriptor((x,y2), model)) && (return (x, y2)) end # check the left and right bounds of the enclosing rectangle x1 = pos[1] - dist @@ -148,12 +151,25 @@ function distanceto(pos::Tuple{Int64,Int64}, model::SimulationModel, habitatdesc miny = maximum((pos[2]-dist+1, 1)) maxy = minimum((pos[2]+dist-1, height)) for y in miny:maxy - (x1 > 0 && habitatdescriptor((x1,y), model)) && (return dist) - (x2 <= width && habitatdescriptor((x2,y), model)) && (return dist) + (x1 > 0 && habitatdescriptor((x1,y), model)) && (return (x1,y)) + (x2 <= width && habitatdescriptor((x2,y), model)) && (return (x2,y)) end dist += 1 end - return Inf + return nothing +end + +""" + distanceto(pos, model, habitatdescriptor) + +Calculate the distance from the given location to the closest location matching the +habitat descriptor function. Caution: can be computationally expensive! +""" +function distanceto(pos::Tuple{Int64,Int64}, model::SimulationModel, habitatdescriptor::Function) + #TODO test + target = directionto(pos, model, habitatdescriptor) + isnothing(target) && return Inf + return maximum(abs.(target-pos)) end """ @@ -195,6 +211,15 @@ function randompixel(pos::Tuple{Int64,Int64}, model::SimulationModel, range::Int nothing end +""" + randomdirection(model, range=1) + +Get a random direction coordinate tuple within the specified range. +""" +function randomdirection(model::SimulationModel, range::Int64=1) + Tuple(@rand(-range:range, 2)) +end + """ inbounds(pos, model)