Boids/Julia
Appearance
< Boids
See Boids
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 rand() > 0.5 && fcenter.x < boid.pos.x - 2 && W in possmoves
move(boid, W)
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