Remote agent/Simulation/Julia

From Rosetta Code
Revision as of 10:53, 9 July 2019 by Wherrera (talk | contribs) (julia example)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

This implementation uses the Gtk GUI library. There are two files: a module and a file to implement the GUI server's Gtk game view.
File: RemoteGameServerClient.jl <lang julia>module RemoteGameServerClient

using Sockets, Distributed, Colors

export Forward, TurnRight, TurnLeft, Get, Drop, GameOver, Stop, SectorRed,

SectorGreen, SectorYellow, SectorBlue, BallRed, BallGreen, BallYellow,
   BallBlue, Bump, SectorFull, AgentFull, NoSectorBall, NoAgentBall

export North, East, South, West, Red, Green, Yellow, Blue, nocolor, Direction

export Pt, configs, asyncserversocketIO, asyncclientsocketIO, makegrid, hasball,

   iswall, inside, Sector, Agent, Grid, directions, sectorcolors, colorsectors,
   colorballs, adjacent, responses, compasspoints

const North = [-1, 0] const East = [0, 1] const South = [1, 0] const West = [0, -1] const Direction = Vector{Int} const compasspoints = [North, East, South, West]

const Forward = '^' const TurnRight = '>' const TurnLeft = '<' const Get = '@' const Drop = '!' const GameOver = '+' const Stop = '.' const SectorRed = 'R' const SectorGreen = 'G' const SectorYellow = 'Y' const SectorBlue = 'B' const BallRed = 'r' const BallGreen = 'g' const BallYellow = 'y' const BallBlue = 'b' const Bump = '|' const SectorFull = 'S' const AgentFull = 'A' const NoSectorBall = 's' const NoAgentBall = 'a'

const Red, Green, Blue, Yellow, nocolor = colorant"red", colorant"green",

   colorant"blue", colorant"yellow", colorant"transparent"

const sectorcolors = [Red, Green, Blue, Yellow] const colorsectors = Dict{Color,Char}(Red => SectorRed, Green => SectorGreen,

   Blue => SectorBlue, Yellow => SectorYellow)

const colorballs = Dict{Color,Char}(Red => BallRed, Green => BallGreen,

   Blue => BallBlue, Yellow => BallYellow)

const configs = Dict{String,Any}("dimensions" => (8, 12), "ballchance" => 0.75,

   "wallchance" => 0.10, "walltile" => '▒', "emptytile" => ' ',
   "balltile" => '\u25cd', "agenttile" => '\u2603', "agentwithball" => '\u26f9',
   "unknowntile" => '?', "ip" => ip"127.0.0.1","portnumber" => 5021,
   "wallcolor" => colorant"darkgray", "unknowncolor" => colorant"gold")

mutable struct Sector

   ch::Char
   clr::Color
   ball::Color

end

struct Pt

   x::Int
   y::Int

end Pt(v::Vector{Int}) = new(v[1], v[2])

mutable struct Agent

   location::Pt
   direction::Direction
   ball:: Color

end

mutable struct Grid

   mat::Matrix{Sector}
   agent::Agent
   turncount::Int64

end

hasball(obj) = obj.ball in sectorcolors inside(m::Matrix{Sector}, x::Int, y::Int) = (0 < x <= size(m)[1]) && (0 < y <= size(m)[2]) inside(m::Matrix{Sector}, p::Pt) = inside(m, p.y, p.y) adjacent(x, y) = [[x - 1, y], [x, y + 1], [x + 1, y], [x, y - 1]] adjacent(p::Pt) = [Pt(a[1], a[2]) for a in adjacent(p.x, p.y)] iswall(s::Sector) = s.ch == configs["walltile"] iswall(m::Matrix{Sector}, x, y) = inside(m, x, y) && iswall(m[x, y]) iswall(m, p::Pt) = iswall(m, p.x, p.y)

allballsmatch(grid) = all(x -> iswall(x) || !hasball(x) || x.clr == x.ball, grid.mat)

function makegrid(height, width)

   wallcolor = configs["wallcolor"]
   m = Matrix{Sector}(undef, height, width)
   probinteriorwall = configs["wallchance"]
   for i in 1:height, j in 1:width
       m[i, j] = ((i == 1) || (j == 1) || (i == height) || (j == width)) ?
           Sector(configs["walltile"], wallcolor, nocolor) :
       m[i, j] = probinteriorwall > rand() ?
           Sector(Char(configs["walltile"]), wallcolor, nocolor) :
           configs["ballchance"] > rand() ?
               Sector(Char(configs["emptytile"]), rand(sectorcolors, 2)...) :
               Sector(Char(configs["emptytile"]), rand(sectorcolors), nocolor)
   end
   for i in 2:height-1, j in 2:width-1 # once walls up, open any isolated sectors
       arr = adjacent(Pt(i, j))
       if !iswall(m, i, j) && all(p -> iswall(m, p), arr)
           m[i, j] = Sector(Char(configs["emptytile"]), rand(sectorcolors, 2)...)
       end
   end
   rows, cols = collect(2:height - 1), collect(2:width-1)
   while true
       i, j = rand(rows), rand(cols)
       if hasball(m[i, j])
           m[i, j] = Sector(configs["emptytile"], m[i, j].clr, nocolor)
           return Grid(m, Agent(Pt(i, j), North, nocolor), 0)
       end
   end

end

function forward!(grid)

   reply = Char[]
   px, py = grid.agent.location.x, grid.agent.location.y
   newx, newy = px + grid.agent.direction[1], py + grid.agent.direction[2]
   if iswall(grid.mat, newx, newy)
       push!(reply, Bump)
   else
       grid.agent.location = Pt(newx, newy)
       sect = grid.mat[newx, newy]
       push!(reply, colorsectors[sect.clr])
       if hasball(sect)
           push!(reply, colorballs[sect.ball])
       end
       grid.turncount += 1
   end
   push!(reply, Stop)
   reply

end

const rightdelta = Dict(North => East, East => South, South => West, West => North) const leftdelta = Dict(p[2] => p[1] for p in rightdelta) rightturn!(grid) = begin grid.agent.direction = rightdelta[grid.agent.direction]; [Stop] end leftturn!(grid) = begin grid.agent.direction = leftdelta[grid.agent.direction]; [Stop] end

function getball!(grid)

   reply = Char[]
   grid.turncount += 1
   if hasball(grid.agent)
       push!(reply, AgentFull)
   else
       p = grid.agent.location
       sect = grid.mat[p.x, p.y]
       if hasball(sect)
           grid.agent.ball = sect.ball
           sect.ball = nocolor
       else
           push!(reply, NoSectorBall)
       end
   end
   push!(reply, Stop)
   reply

end

function dropball!(grid)

   reply = Char[]
   grid.turncount += 1
   if !hasball(grid.agent)
       push!(reply, NoAgentBall)
   else
       p = grid.agent.location
       sect = grid.mat[p.x, p.y]
       if !hasball(sect)
           sect.ball = grid.agent.ball
           grid.agent.ball = nocolor
           if allballsmatch(grid)
               push!(reply, GameOver)
           end
       else
           push!(reply, SectorFull)
       end
   end
   push!(reply, Stop)
   reply

end

responses = Dict{Char,Function}(Forward => forward!, TurnRight => rightturn!,

   TurnLeft => leftturn!, Get => getball!, Drop => dropball!)

function asyncserversocketIO(inchan, outchan, debug=false)

   try
       socket = TCPSocket()
       server = listen(configs["ip"], configs["portnumber"])
       debug && println("Listening at ", configs["ip"], " ", configs["portnumber"])
       socket = accept(server)
       debug && println("Connected at ", configs["ip"], " ", configs["portnumber"])
       while isopen(socket) && isopen(inchan) && isopen(outchan)
           ch = read(socket, Char)
           put!(inchan, ch) 
           debug && println("socket sent to inchan: $ch")
           yield()
           while (ch = take!(outchan)) != '.'
               write(socket, ch)
               flush(socket)
               debug && println("outsocket: $ch")
           end
           write(socket, ch)
           flush(socket)
           debug && println("outsocket: $ch")
       end
   catch y
       @warn("Socket IO: caught exception $y")
   end

end

function asyncclientsocketIO(inchan, outchan, debug=false)

   try
       debug && println("Trying connection at ", configs["ip"], " ", configs["portnumber"])
       socket = connect(configs["ip"], configs["portnumber"])
       debug && println("Connected at ", configs["ip"], " ", configs["portnumber"])
       while isopen(socket) && isopen(inchan) && isopen(outchan)
           if !isready(outchan)
               sleep(0.05)
               yield()
           else
               while isready(outchan)
                   ch = take!(outchan)
                   debug && println("outsocket: $ch")
                   write(socket, ch)
                   flush(socket)
               end
               while (ch = read(socket, Char)) != '.'
                   put!(inchan, ch)
                   debug && println("inchan: $ch")
               end
               put!(inchan, ch)
               debug && println("inchan: $ch")             
           end
       end
   catch y
       println("Socket IO: caught exception $y")
   end

end

end # module RemoteGameServerClient


  1. modulino for testing

if occursin(Base.PROGRAM_FILE, @__FILE__)

using .RemoteGameServerClient

function evalcommand(grid, inchan, outchan, debug=false)

   debug && println("Starting command evaluation service.")
   debug && println("Configuration settings are: $(RemoteGameServerClient.configs).")
   while isopen(inchan) && isopen(outchan)
       ch = take!(inchan)
       debug && println("responding to $ch as $(channelcodes[ch])")
       response = responses[channelcodes[ch]](grid)
       debug && println(" and $response")
       debug && println("Got code: $ch.  Response is: $response")
       for code in response
           ch = streamcodes[code]
           put!(outchan, ch)
       end
       if GameOver in response
           close(inchan)
           close(outchan)
           break
       end
   end

end

function newgame()

   serverin, serverout = Channel{Char}(100), Channel{Char}(100)
   @async asyncserversocketIO(serverin, serverout)
   grid = makegrid(10, 16)
   evalcommand(grid, serverin, serverout, true)

end

newgame()

end # modulino for testing </lang> Second file: gtk_remoteserversimulation.jl <lang julia>using Distributed, Sockets, Gtk, Colors, Graphics, Cairo

include("RemoteGameServerClient.jl")

using .RemoteGameServerClient

function matchballsgameserver()

   # The player is not to know what direction they are facing, so as to not
   # give them more information about the map at start. However, we ideally would
   # want the map as learned by agent to be aligned with the map known to server.
   # To do so, we start agent facin north, but in a random location with the map
   # height and width randomly excahnged, so knowing facing north gives no net
   # information about the shape and contents of the grid.
   height, width = configs["dimensions"]
   if rand() > 0.5
       height, width = width, height
   end
   fontsize = configs["dimensions"][1] * 2
   win = GtkWindow("Match Color Balls Game      ", 600, 600) |> (GtkFrame() |> (box = GtkBox(:v)))
   can = GtkCanvas()
   set_gtk_property!(can, :expand, true)
   push!(box, can)
   grid = makegrid(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_font_size(ctx, fontsize)
       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]
           fill(ctx)
           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
       end
   end
   function evalcommand(grid, inchan, outchan, debug=false)
       debug && println("Starting command evaluation service.")
       debug && println("Configuration settings are: $(RemoteGameServerClient.configs).")
       while isopen(inchan) && isopen(outchan)
           ch = take!(inchan)
           debug && println("responding to $ch input from client")
           response = responses[ch](grid)
           debug && println(" and response is then $response")
           draw(can)
           show(can)
           show(win)
           debug && println("screen update done")
           for ch in response
               put!(outchan, ch)
           end
           if GameOver in response
               close(inchan)
               close(outchan)
               break
           end
       end
   end
   
   serverin, serverout = Channel{Char}(100), Channel{Char}(100)
   @async asyncserversocketIO(serverin, serverout, false)
   @async evalcommand(grid, serverin, serverout, false)
   
   condition = Condition()
   endit(w) = notify(condition)
   signal_connect(endit, win, :destroy)
   showall(win)
   wait(condition)

end

matchballsgameserver() </lang>