Remote agent/Agent interface/Julia
See Remote_agent/Agent_interface
In order to do the agent task in two parts, the implementation below is crippled: it leaves out the ball handling code. The simulated agent interacts with the server to explore the game board, mapping the board visibly on the Gtk game app. The third part of the simulation task implements the logic for actually winning the game.
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]]
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
function chooseforward(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"]]
if nearby[dirorder[ag.direction]] == configs["unknowntile"]
return [Forward]
elseif length(allunknown) > 0
return vcat(turn(grid, ag.direction, rand(allunknown)), [Forward])
elseif length(allempty) > 0
return vcat(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
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 => nullexec,
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)
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)
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)
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, true)
condition = Condition()
endit(w) = notify(condition)
signal_connect(endit, win, :destroy)
showall(win)
wait(condition)
end
matchballsgameclient()