Robots/Julia

From Rosetta Code

Julia
Uses the Gtk library.

using Gtk, Colors, Cairo, Graphics

const fontpointsize = 10
const mapwidth = 900
const mapheight = 700
const leveldim = div(mapwidth, fontpointsize)
const windowmaxx = div(mapheight, fontpointsize)
const windowmaxy = leveldim

win = GtkWindow("Robots Game  (H for Help)", mapwidth, mapheight) |> (GtkFrame() |> (vbox = GtkBox(:v)))
set_gtk_property!(vbox, :expand, true)
can = GtkCanvas(div(mapwidth, 3), mapheight)
push!(vbox, can)

const playerchar, robotchar, hazardchar, wallchar, spacechar = '@', '+', '\u2623', '\u2593', ' '
const itemcolors = Dict{Char, Colorant}('@' => colorant"white", ' ' => colorant"black",
    '\u2623' => colorant"gold", '+' => colorant"red", '\u2593' => colorant"silver")
const debrisprobability, debrisdiameter, maxrobots, gravechar = 0.005, 6, 10, '\u271d'
const gameover = [false]

const grid = fill(spacechar, windowmaxx, windowmaxy)

@guarded draw(can) do widget
    ctx = 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(grid)[2]
    workbuf = Char[]
    for i in 1:size(grid)[1]
        move_to(ctx, 0, i * fontpointsize)
        lastcharprinted = '\x01'
        for j in 1:linelen
            ch = grid[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

struct Point
    x::Int
    y::Int
end

randomlocation() = Point(rand(2:windowmaxx-1), rand(2:windowmaxy))

function randomdiameterfill(n, center, ch)
    d = rand(1:n)
    surround = [Point(i + center.x, j + center.y) for i in -d:d, j in -d:d]
    for p in surround
        if p.x > 1 && p.x < windowmaxx -1 && p.y > 1 && p.y < windowmaxy -1
            if rand() * d > sqrt((center.x - p.x)^2 + (center.y - p.y)^2)
                grid[p.x, p.y] = ch
            end
        end
    end
end

function randomemptypoint()
    for i in 1:100000
        pt = randomlocation()
        if grid[pt.x, pt.y] == spacechar
            return pt
        end
    end
    throw("Cannot find an empty point in the grid")
end

mutable struct Player
    location::Point
    movenumber::Int
    lastteleport::Int
    Player() = new(randomemptypoint(), 1, 0)
end

mutable struct Robot
    location::Point
    active::Bool
end

function setupgrid()
    x1, y1, x2, y2, robots = 1, 1, windowmaxx, windowmaxy, Robot[]
    grid[x1:x2, y1:y2] .= ' '
    grid[x1:x2, y1] .= wallchar
    grid[x1:x2, y2] .= wallchar
    grid[x1, y1:y2] .= wallchar
    grid[x2, y1:y2] .= wallchar
    for x in x1:x2, y in y1:y2
        if rand() < debrisprobability
            randomdiameterfill(debrisdiameter, Point(x, y), hazardchar)
        end
    end
    for _ in 1:maxrobots
        p = randomemptypoint()
        grid[p.x, p.y] = robotchar
        push!(robots, Robot(p, true))
    end
    return Player(), robots
end

function updategrid!(player)
    grid[player.location.x, player.location.y] = playerchar
    Gtk.showall(win)
    draw(can)
    Gtk.show(can)
end

function help(player)
    info_dialog("W up, S down, A left, D right, Spacebar teleport (every 12 moves).\n" *
                     " Avoid Robots and Hazards! Good Luck!", win)
end

playerwins(p) = warn_dialog("You have WON the game!\nTotal moves: $(p.movenumber)", win)
playerloses(p) = warn_dialog("You have lost the game.\nTotal moves: $(p.movenumber)", win)

function destroyrobot!(robots, x, y)
    if grid[x, y] == robotchar
        grid[x, y] = hazardchar
        for (i, robot) in enumerate(robots)
            if robot.location.x == x && robot.location.y == y
                robots[i].active = false
                break
            end
        end
    end
end

function moverobot!(robots, robot, dx, dy, player)
    (dx == 0 && dy == 0) && return false
    p = Point(robot.location.x + dx, robot.location.y + dy)
    if grid[p.x, p.y] == wallchar
        return false
    elseif grid[p.x, p.y] == spacechar     # robot moves a space
        grid[robot.location.x, robot.location.y] = spacechar
        grid[p.x, p.y] = robotchar
        robot.location = Point(p.x, p.y)
        return true
    elseif grid[p.x, p.y] == robotchar  # robots collide
        destroyrobot!(robots, robot.location.x, robot.location.y)
        destroyrobot!(robots, p.x, p.y)
    elseif grid[p.x, p.y] == hazardchar # robot hits hazard
        destroyrobot!(robots, robot.location.x, robot.location.y)
    elseif grid[p.x, p.y] == playerchar # robot hits player
        grid[p.x, p.y] = gravechar
        gameover[1] = true
        playerloses(player)
    end
    if all(r -> !r.active, robots)
        gameover[1] = true
        updategrid!(player)
        playerwins(player)
    end
    return false
end

function robotsmove!(robots, player)
    for robot in robots
        if robot.active
            dx = player.location.x - robot.location.x
            dy = player.location.y - robot.location.y
            xmove = dx > 0 ? 1 : dx == 0 ? 0 : -1
            ymove = dy > 0 ? 1 : dy == 0 ? 0 : -1
            if !moverobot!(robots, robot, xmove, ymove, player)
                moverobot!(robots, robot, rand([-1, 0, 1]), rand([-1, 0, 1]), player)
            end
        else
            grid[robot.location.x, robot.location.y] = hazardchar
        end
    end
end

function playerteleport(player)
    if player.lastteleport < player.movenumber
        p = randomemptypoint()
        grid[player.location.x, player.location.y] = spacechar
        grid[p.x, p.y] = playerchar
        player.location = p
        player.movenumber += 1
        player.lastteleport = player.movenumber + 12
        updategrid!(player)
    end
end

function playermove(player, dx, dy)
    goalp = Point(player.location.x + dx, player.location.y + dy)
    if grid[goalp.x, goalp.y] != wallchar
        if grid[goalp.x, goalp.y] == hazardchar || grid[goalp.x, goalp.y] == robotchar
            grid[player.location.x, player.location.y] = gravechar
            gameover[1] = true
            updategrid!(player)
            playerloses(player)
        else
            grid[player.location.x, player.location.y] = spacechar
            player.location = Point(goalp.x, goalp.y)
            grid[goalp.x, goalp.y] = playerchar
        end
    end
    player.movenumber += 1
end
playerN(player) = playermove(player, -1, 0)
playerW(player) = playermove(player, 0, -1)
playerS(player) = playermove(player, 1, 0)
playerE(player) = playermove(player, 0, 1)

const usercommands = Dict("W" => playerN, "S" => playerS, "D" => playerE, "A" => playerW,
                          " " => playerteleport, "H" => help)

const inputchan = Channel{String}(1000)
kbinput(w, event) = (push!(inputchan, string(Char(event.keyval))); 1)
const inputhid = signal_connect(kbinput, win, "key-press-event")
getinput(choices) = while true if (c = uppercase(take!(inputchan))) in choices return c end; end

function rungame()   
    player, robots = setupgrid()
    while !gameover[1]
        robotsmove!(robots, player)
        updategrid!(player)
        choice = getinput(keys(usercommands))
        usercommands[choice](player)
    end
end

rungame()