Boids/Julia
<lang julia>using Gtk.ShortNames, Colors, Cairo, Graphics
const fontpointsize = 10 const mapwidth = 1000 const mapheight = 500 const windowmaxx = div(mapwidth, Int(round(fontpointsize * 0.92))) const windowmaxy = div(mapheight, fontpointsize) const basebuffer = fill(' ', windowmaxy, windowmaxx)
win = Window("Boids", mapwidth, mapheight) |> (can = Canvas()) set_gtk_property!(can, :expand, true)
@guarded Gtk.draw(can) do widget
ctx = Gtk.getgc(can) select_font_face(ctx, "Courier", Cairo.FONT_SLANT_NORMAL, Cairo.FONT_WEIGHT_BOLD) set_font_size(ctx, fontpointsize) workcolor = colorant"black" set_source_rgb(ctx, 0.2, 0.2, 0.2) rectangle(ctx, 0, 0, mapwidth, mapheight) fill(ctx) color = colorant"white" set_source(ctx, color) linelen = size(basebuffer)[2] workbuf = Char[] for i in 1:size(basebuffer)[1] move_to(ctx, 0, i * fontpointsize) lastcharprinted = '\x01' for j in 1:linelen ch = basebuffer[i, j] if j == 1 lastcharprinted = ch elseif ch != lastcharprinted show_text(ctx, String(workbuf)) empty!(workbuf) end if haskey(itemcolors, ch) && itemcolors[ch] != color color = itemcolors[ch] set_source(ctx, color) end push!(workbuf, ch) if j == linelen show_text(ctx, String(workbuf)) empty!(workbuf) end end end
end
@enum Directions NW N NE E SE S SW W Here
const defaultdirection = E
boidmoves = Dict{Directions, Vector{Int}}(Here => [0, 0], NW => [-1, -1], N => [0, -1], NE => [1, -1],
E => [1, 0], SE => [1, 1], S => [0, 1], SW => [-1, 1], W => [-1, 0])
struct Point
x::Int y::Int
end
mutable struct Obstacle
occupied::Vector{Point}
end
mutable struct Walls
occupied::Vector{Point}
end
mutable struct Environment
width::Int height::Int walls::Walls obstacles::Vector{Obstacle} buffer::Matrix{Char}
end
ebuf(e, p::Point) = e.buffer[p.x, p.y]
mutable struct Boid
pos::Point flock::Vector{Boid}
end
aschar(b::Boid) = 'o' aschar(o::Obstacle) = '*' aschar(w::Walls) = '\u2593'
function buildwalls(environ)
for i in 1:environ.height push!(environ.walls.occupied, Point(1, i), Point(environ.width, i)) end for i in 1:environ.width push!(environ.walls.occupied, Point(i, 1), Point(i, environ.height)) end for p in environ.walls.occupied if 0 < p.x <= environ.width && 0 < p.y <= environ.height environ.buffer[p.y, p.x] = aschar(environ.walls) end end
end
inellipse(dx, dy, a, b) = (dx / a)^2 + (dy / b)^2 < 1.0 inellipseat(p, x, y, a, b) = inellipse(x - p.x, y - p.y, a, b)
function buildobstacles(environ, n=5)
widthoptions = collect(3:max(5, div(environ.height, 10))) heightoptions = collect(7:max(10, div(environ.height, 2))) for i in 1:n obst = Obstacle(Point[]) push!(environ.obstacles, obst) w, h = rand(widthoptions), rand(heightoptions) w, h = (w > h) ? (h, w) : (w, h) center = Point(Int(round(environ.width * rand())), Int(round(environ.height * rand()))) for y in center.y-h:center.y+h, x in center.x-w:center.x+w if inellipseat(center, x, y, w, h) && 1 < x < environ.width && 1 < y < environ.height push!(obst.occupied, Point(x, y)) environ.buffer[y, x] = aschar(obst) end end end
end
function buildenvironment()
environ = Environment(windowmaxx, windowmaxy, Walls(Point[]), Obstacle[], basebuffer) buildwalls(environ) buildobstacles(environ) environ
end
function addflock(allflocks, numboids, environ)
f = Vector{Boid}() center = Point(12, div(environ.height, 2)) varpick = collect(-10:10) while length(f) < numboids while true newboid = Boid(Point(center.x + rand(varpick), center.y + rand(varpick)), f) if all(x -> x.pos != newboid.pos, f) && environ.buffer[newboid.pos.y, newboid.pos.x] == ' ' push!(f, newboid) break end end end push!(allflocks, f)
end
function availablemoves(boid, environ)
avail = Vector{Directions}() for (direc, v) in boidmoves if environ.buffer[boid.pos.y + v[2], boid.pos.x + v[1]] == ' ' push!(avail, direc) end end avail
end
function center(flock)
xs, ys, n = 0, 0, length(flock) for b in flock xs += b.pos.x ys += b.pos.y end Point(Int(round(xs / n)), Int(round(ys / n)))
end
isobstacle(x, y, environ) = (c = environ.buffer[y, x]; c != '*' && c != '\u2593') nextobstaclex(pos, e) = (x = pos.x + 1; while e.buffer[pos.y, x] == ' ' x += 1 end; x)
nearobs(b, e, delt=8) = b.pos.x - nextobstaclex(b.pos, e) < delt atobs(b, e) = e.buffer[b.pos.y, b.pos.x + 1] != ' '
function nearbyopen(b, e, opendist = e.width)
dist, y = findmax([nextobstaclex(Point(b.pos.x, y), e) for y in 1:e.height]) return y
end
function move(boid, d::Directions)
m = boidmoves[d] boid.pos = Point(boid.pos.x + m[1], boid.pos.y + m[2])
end
showboid(b, environ) = begin x, y = b.pos.x, b.pos.y; environ.buffer[y, x] = aschar(b) end hideboid(b, environ) = begin x, y = b.pos.x, b.pos.y; environ.buffer[y, x] = ' ' end showmove(b, e) = begin hideboid(b, e); move(b, e); showboid(b, e) end
function move(boid, environ::Environment)
possmoves = availablemoves(boid, environ) fcenter = center(boid.flock) wantsouth, wantnorth = false, false if atobs(boid, environ) wanty = nearbyopen(boid, environ, 8) d = wanty > boid.pos.y && S in possmoves ? S : N in possmoves ? N : rand(possmoves) move(boid, d) return end if rand() > 0.5 && nearobs(boid, environ) wanty = nearbyopen(boid, environ, 8) d = wanty > boid.pos.y && SE in possmoves ? SE : NE in possmoves ? NE : E move(boid, d) return end if fcenter.y > boid.pos.y + 1 wantsouth = true elseif fcenter.y < boid.pos.y - 1 wantnorth = true end if wantsouth if SE in possmoves move(boid, SE) elseif S in possmoves move(boid, S) end elseif wantnorth if NE in possmoves move(boid, NE) elseif N in possmoves move(boid, N) end elseif E in possmoves move(boid, E) end
end
const itemcolors = Dict{Char, Colorant}('o' => colorant"white", ' ' => colorant"black", '*' => colorant"gold",
'\u2593' => colorant"silver")
environ = buildenvironment() const allflocks = Vector{Vector{Boid}}() addflock(allflocks, 5, environ)
draw(can) show(can)
while true
sleep(0.5) for flock in allflocks, boid in flock showmove(boid, environ) end draw(can)
end </lang>