Remote agent/Agent logic/Julia

From Rosetta Code

See Remote_agent/Agent_logic

The agent uses a random walk algorithm, to which is added a preference for unknown tiles, to explore the board, and the following:
When the agent does not have a ball, the agent will tend to move toward a sector on a straight line path from agent (if such a sector has a mismatch between sector and ball colors), to get the next ball for agent.
When the agent holds a ball, it will have a preference for straight line paths leading to an empty sector the color of the agent's ball in order to drop the ball.
If the agent carries the same ball for over 25 moves, it will drop that ball and look for a differently colored ball.

using Distributed, Gtk, Colors, Cairo

include("RemoteGameServerClient.jl")

using .RemoteGameServerClient

const dirorder = Dict(North => 1, East => 2, South => 3, West => 4)
const orderdir = Dict(1 => North, 2 => East, 3 => South, 4 => West)
const rightturns = [[North, East], [East, South], [South, West], [West, North]]
const leftturns = [[East, North], [South, East], [West, South], [North, West]]
const turnpi = [[East, West], [South, North], [West, East], [North, South]]
const dlabl = Dict(North => "north", East => "east", South => "south", West => "west")
const param = Dict("won" => false, "carriedsame" => 0, "bannedball" => nocolor)

function ballhandler(grid)
    sector = grid.mat[grid.agent.location.x, grid.agent.location.y]
    agent = grid.agent
    if rand() > 0.8
        param["bannedball"] = nocolor # banned color is one that was hard to drop last time
    end
    if sector.ch == configs["emptytile"] && sector.clr != sector.ball
        if (sector.ball == nocolor && agent.ball == sector.clr) ||
            (sector.ball == nocolor && agent.ball != nocolor && param["carriedsame"] > 25)
            sector.ball = agent.ball
            agent.ball = nocolor
            param["carriedsame"] = 0 # was able to drop this color
            param["bannedball"] = (sector.clr != sector.ball) ? sector.ball : nocolor
            return [Drop]
        elseif sector.ball != nocolor && agent.ball == nocolor && 
                    sector.ball != param["bannedball"]
            agent.ball = sector.ball
            sector.ball = nocolor
            return [Get]
        end
    end
    Char[]
end

function turn(grid, from::Direction, to::Direction)
    grid.agent.direction = to
    [from, to] in rightturns ? [TurnRight] :
    [from, to] in leftturns ? [TurnLeft] :
    [from, to] in turnpi ? [TurnRight, TurnRight] :
    Char[]
end

forhaswrongball(sector, agent) = sector.ball == nocolor && sector.clr == agent.ball
forhasnoball(sector, agent) = sector.ball != nocolor && sector.clr != sector.ball

function sectortoward(grid, ax, ay, dir, f)
    x, y = ax + dir[1], ay + dir[2]
    while grid.mat[x, y].ch == configs["emptytile"]
        if f(grid.mat[x, y], grid.agent)
            return true
        end
        x, y = x + dir[1], y + dir[2]
    end
    return false
end

function chooseforward(grid)
    ret = ballhandler(grid)
    ag = grid.agent
    nearby = [grid.mat[a[1], a[2]].ch for a in adjacent(ag.location.x, ag.location.y)]
    allunknown = [orderdir[i] for i in 1:length(nearby) if nearby[i] == configs["unknowntile"]]
    allempty = [orderdir[i] for i in 1:length(nearby) if nearby[i] == configs["emptytile"]]
    grid.turncount += 1
    if ag.ball != nocolor
        param["carriedsame"] += 1
    end
    if nearby[dirorder[ag.direction]] == configs["unknowntile"]
        return vcat(ret, [Forward])
    elseif length(allunknown) > 0
        return vcat(ret, turn(grid, ag.direction, rand(allunknown)), [Forward])
    elseif length(allempty) > 0
        x, y = ag.location.x, ag.location.y
        f = (ag.ball == nocolor) ? forhasnoball : forhaswrongball
        for dir in allempty
            if sectortoward(grid, x, y, dir, f)
                return vcat(ret, turn(grid, ag.direction, dir), [Forward])
            end
        end
        return vcat(ret, turn(grid, ag.direction, rand(allempty)), [Forward])
    else
        throw("Cannot find a way out from location ", ag.location)
    end
end

function makeunknowngrid(height, width)
    m = Matrix{Sector}(undef, height, width)
    for i in 1:height, j in 1:width
        m[i, j] = ((i == 1) || (j == 1) || (i == height) || (j == width)) ?
            Sector(configs["walltile"], configs["wallcolor"], nocolor) :
            Sector(configs["unknowntile"], configs["unknowncolor"], nocolor)
    end
    Grid(m, Agent(Pt(height ÷ 2, width ÷ 2), North, nocolor), 0)
end

const scolordict = Dict(p[2] => p[1] for p in colorsectors)
const ballcolordict = Dict(lowercase(p[2]) => p[1] for p in colorsectors)
nullexec(c, g, s, l) = ()
warnexec(c, g, s, l) = @warn("Server told client command was in error because $c")
ballexec(c, g, s, l) = begin s.ball = ballcolordict[c] end
wonexec(c, g, s, l) = param["won"] = true

function bumpexec(c, g, s, l)
    if s.ch == configs["emptytile"]
        throw("Bump for a sector at $newlocation we marked empty ($newsector)")
    end
    s.ch, s.clr, s.ball = configs["walltile"], configs["wallcolor"], nocolor
end

function intosectorexec(c, g, s, l)
    if s.ch == configs["walltile"]
        throw("The sector at $l was marked as a wall, now is marked empty")
    end
    g.agent.location = l
    s.ch = configs["emptytile"]
    s.clr = scolordict[c]
end

const charexec = Dict{Char, Function}(GameOver => wonexec,
    Bump => bumpexec, SectorFull => warnexec, AgentFull => warnexec,
    NoSectorBall => warnexec, NoAgentBall => warnexec,
    SectorRed => intosectorexec, SectorGreen => intosectorexec,
    SectorYellow => intosectorexec, SectorBlue => intosectorexec,
    BallRed => ballexec, BallGreen => ballexec,
    BallYellow => ballexec, BallBlue => ballexec)

function processreplies(chars, grid)
    newx = grid.agent.location.x + grid.agent.direction[1]
    newy = grid.agent.location.y + grid.agent.direction[2]
    newsector, newlocation = grid.mat[newx, newy], Pt(newx, newy)
    for cmd in chars
        if cmd == Stop
            break
        end
        charexec[cmd](cmd, grid, newsector, newlocation)
    end
end

function matchballsgameclient()
    # The agent starts facing north, but in a random location on map, with the map
    # height and width randomly excahnged. Therefore, starting facing north gives
    # no useful information about the map contents, since its shape is not known.
    height = maximum(configs["dimensions"]) * 2 # big enough for any
    width, fontsize = height, configs["dimensions"][1] * 2
    win = GtkWindow("Match Color Balls Game Client Running     ", 600, 600) |>
        (GtkFrame() |> (box = GtkBox(:v)))
    can = GtkCanvas()
    set_gtk_property!(can, :expand, true)
    push!(box, can)
    grid = makeunknowngrid(height, width)

    @guarded Gtk.draw(can) do widget
        ctx = Gtk.getgc(can)
        select_font_face(ctx, "Courier", Cairo.FONT_SLANT_NORMAL, Cairo.FONT_WEIGHT_BOLD)
        set_source_rgb(ctx, 0.2, 0.2, 0.2)
        l = fontsize * 2.5
        for i in 1:size(grid.mat)[1], j in 1:size(grid.mat)[2]
            set_source(ctx, grid.mat[i, j].clr)
            rectangle(ctx, (i - 1) * l, (j - 1) * l, l, l)
            fill(ctx)
            if hasball(grid.mat[i, j])
                set_source(ctx, grid.mat[i, j].ball)
                circle(ctx, (i - 1) * l + fontsize * 1.25, (j - 1) * l + fontsize * 1.25, fontsize)
                fill(ctx)
                set_source(ctx, colorant"gray")
                arc(ctx, (i - 1) * l + fontsize * 1.25, (j - 1) * l + fontsize * 1.25, fontsize, 0, 2*pi)
                stroke(ctx)
            end
            if Pt(i, j) == grid.agent.location
                move_to(ctx, (i - 1) * l + fontsize * 1.25, (j - 1) * l + fontsize * 0.2)
                set_source(ctx, colorant"silver")
                line_to(ctx, i * l, j * l)
                line_to(ctx, (i - 1) * l, j * l)
                line_to(ctx, (i - 1) * l + fontsize * 1.25, (j - 1) * l + fontsize * 0.2)
                stroke(ctx)
                set_source(ctx, (grid.agent.ball == nocolor) ? colorant"black" : grid.agent.ball)
                circle(ctx, (i - 1) * l + fontsize * 1.25, (j - 1) * l + fontsize * 2, fontsize / 4)
                fill(ctx)
                set_source(ctx, colorant"silver")
                arc(ctx, (i - 1) * l + fontsize * 1.25, (j - 1) * l + fontsize * 2, fontsize / 4, 0, 2*pi)
                stroke(ctx)
            end
            if grid.mat[i, j].ch == configs["unknowntile"]
                move_to(ctx, (i - 1) * l + fontsize * 0.8 , (j - 1) * l + fontsize * 2)
                set_font_size(ctx, fontsize * 1.5)
                set_source(ctx, colorant"silver")
                show_text(ctx, "?")
            end
        end
    end

    function clientsendcommand(grid, inchan, outchan, debug=false)
        debug && println("Starting client game play.")
        debug && println("Configuration settings are: $(RemoteGameServerClient.configs).")

        while isopen(inchan) && isopen(outchan)
            draw(can)
            show(can)
            show(win)
            debug && println("Currently agent is facing ", grid.agent.direction, " holding ", grid.agent.ball)
            cmds = chooseforward(grid)
            for ch in cmds
                draw(can)
                show(can)
                show(win)
                debug && println("Sending command as char $ch")
                put!(outchan, ch)
                ch, reply = '\0', Char[]
                while (ch = take!(inchan)) != '.'
                    push!(reply, ch)
                end
                push!(reply, ch) # '.'
                debug && println("clientsendcommand: Reply from server is $reply")
                processreplies(reply, grid)
                if param["won"] == true
                    println("Game over, agent won in ", grid.turncount, " moves.")
                    warn_dialog("You have WON the game!\nTotal moves: $(grid.turncount)", win)
                    close(inchan)
                    close(outchan)
                end
            end
        end
    end

    grid = makeunknowngrid(height, width)
    serverin, serverout = Channel{Char}(100), Channel{Char}(100)
    @async asyncclientsocketIO(serverin, serverout, false)
    @async clientsendcommand(grid, serverin, serverout, false)

    condition = Condition()
    endit(w) = notify(condition)
    signal_connect(endit, win, :destroy)
    showall(win)
    wait(condition)
end

matchballsgameclient()