Remote agent/Agent logic/Julia

From Rosetta Code
Revision as of 18:43, 12 July 2019 by Wherrera (talk | contribs) (avoid certain unwinnable positions)

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. <lang julia>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() </lang>