⚠️ Warning: This is a draft ⚠️
This means it might contain formatting issues, incorrect code, conceptual problems, or other severe issues.
If you want to help to improve and eventually enable this page, please fork RosettaGit's repository and open a merge request on GitHub.
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()