Remote agent/Agent interface: Difference between revisions

From Rosetta Code
Content added Content deleted
(Added Wren)
m (→‎{{header|Wren}}: Changed to Wren S/H)
 
(3 intermediate revisions by 2 users not shown)
Line 12: Line 12:


=={{header|Go}}==
=={{header|Go}}==
<lang go>package ifc
<syntaxhighlight lang="go">package ifc


// Streamer interface type defines how agent and world talk to each other.
// Streamer interface type defines how agent and world talk to each other.
Line 49: Line 49:
EvNoBallInSector = 's'
EvNoBallInSector = 's'
EvNoBallInAgent = 'a'
EvNoBallInAgent = 'a'
)</lang>
)</syntaxhighlight>
Above is the interface package defining the abstract interface. Below is an example of a driver program that supplies a concrete implementation of the interface. This implementation uses Go channels to run a simulation in a single process. A different implementation might produce two executables that would run on different processes or different machines. It would use the same interface however.
Above is the interface package defining the abstract interface. Below is an example of a driver program that supplies a concrete implementation of the interface. This implementation uses Go channels to run a simulation in a single process. A different implementation might produce two executables that would run on different processes or different machines. It would use the same interface however.
<lang go>package main
<syntaxhighlight lang="go">package main


import (
import (
Line 99: Line 99:
// point. The program terminates immediately when World returns.
// point. The program terminates immediately when World returns.
world.World(chanStreamer{"world", cmd, ev})
world.World(chanStreamer{"world", cmd, ev})
}</lang>
}</syntaxhighlight>
A separate file, but still part of the driver, contains human readable text for commands and events. Alternatively, this file could be included with the interface package for maintainability, but technically it is not a required part of the abstract interface used by the agent and world simulator.
A separate file, but still part of the driver, contains human readable text for commands and events. Alternatively, this file could be included with the interface package for maintainability, but technically it is not a required part of the abstract interface used by the agent and world simulator.
<lang go>package main
<syntaxhighlight lang="go">package main


import "ra/ifc"
import "ra/ifc"
Line 127: Line 127:
ifc.EvNoBallInSector: "event no ball in sector",
ifc.EvNoBallInSector: "event no ball in sector",
ifc.EvNoBallInAgent: "event no ball in agent",
ifc.EvNoBallInAgent: "event no ball in agent",
}</lang>
}</syntaxhighlight>




Line 136: Line 136:
=== abstract interface ===
=== abstract interface ===
Modify this to include one of the two concrete implementations below (but not both!)
Modify this to include one of the two concrete implementations below (but not both!)
<!--<lang Phix>(notonline)-->
<!--<syntaxhighlight lang="phix">(notonline)-->
<span style="color: #000080;font-style:italic;">--
<span style="color: #000080;font-style:italic;">--
-- demo\rosetta\Remote_Agent_Interface.exw
-- demo\rosetta\Remote_Agent_Interface.exw
Line 227: Line 227:
-- in a suitable format for use as per fn() in show() above.
-- in a suitable format for use as per fn() in show() above.
--</span>
--</span>
<!--</lang>-->
<!--</syntaxhighlight>-->


=== Interface_Direct ===
=== Interface_Direct ===
This implementation of the above interface is a single standalone program
This implementation of the above interface is a single standalone program
<!--<lang Phix>(notonline)-->
<!--<syntaxhighlight lang="phix">(notonline)-->
<span style="color: #000080;font-style:italic;">--
<span style="color: #000080;font-style:italic;">--
-- demo\rosetta\Remote_Agent_Interface_Direct.e
-- demo\rosetta\Remote_Agent_Interface_Direct.e
Line 281: Line 281:
<span style="color: #008080;">if</span> <span style="color: #000000;">accept_command</span><span style="color: #0000FF;">!=</span><span style="color: #000000;">0</span> <span style="color: #008080;">then</span> <span style="color: #000000;">main_event_loop</span><span style="color: #0000FF;">()</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">if</span> <span style="color: #000000;">accept_command</span><span style="color: #0000FF;">!=</span><span style="color: #000000;">0</span> <span style="color: #008080;">then</span> <span style="color: #000000;">main_event_loop</span><span style="color: #0000FF;">()</span> <span style="color: #008080;">end</span> <span style="color: #008080;">if</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<!--</lang>-->
<!--</syntaxhighlight>-->


=== Interface_IPC ===
=== Interface_IPC ===
This implementation of the above interface lets two separate programs communicate using ipc
This implementation of the above interface lets two separate programs communicate using ipc
<!--<lang Phix>(notonline)-->
<!--<syntaxhighlight lang="phix">(notonline)-->
<span style="color: #000080;font-style:italic;">--
<span style="color: #000080;font-style:italic;">--
-- demo\rosetta\Remote_Agent_Interface_IPC.e
-- demo\rosetta\Remote_Agent_Interface_IPC.e
Line 396: Line 396:
<span style="color: #008080;">end</span> <span style="color: #008080;">while</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">while</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<span style="color: #008080;">end</span> <span style="color: #008080;">procedure</span>
<!--</lang>-->
<!--</syntaxhighlight>-->


=={{header|PicoLisp}}==
=={{header|PicoLisp}}==
Line 403: Line 403:
=={{header|Tcl}}==
=={{header|Tcl}}==
{{works with|Tcl|8.6}}
{{works with|Tcl|8.6}}
<lang tcl>package require Tcl 8.6
<syntaxhighlight lang="tcl">package require Tcl 8.6


oo::class create AgentAPI {
oo::class create AgentAPI {
Line 526: Line 526:


# Export as package
# Export as package
package provide RC::RemoteAgent 1</lang>
package provide RC::RemoteAgent 1</syntaxhighlight>


=={{header|Wren}}==
=={{header|Wren}}==
{{trans|Go}}
{{trans|Go}}
{{libheader|Wren-str}}
{{libheader|Wren-fmt}}
Unlike the Go entry, I've placed the 'human readable text' code inside the 'ifc' module and have also placed a basic logging facility in there too as its called by both the 'world' and 'agent' modules. It always prints to stdout.
<lang ecmascript>/* agent.wren */


I've changed the 'handshake' symbol from "A" to "H" to distinguish it from 'evAgentFull'.
import "random" for Random
import "./ifc" for Ifc, Log
import "./str" for Char


The 'driver code' has been moved to the [[Remote_agent/Simulation]] task.
// The agent's awareness is quite limited. It has no representation of the
<syntaxhighlight lang="wren">/* ifc.wren */
// maze, which direction it is facing, or what it did last. It notices and
// remembers just three things: The color of the sector just entered, the
// presence and color of any ball there, and the presence and color of any
// ball it is holding.
var sectorColor = " "
var sectorBall = " "
var agentBall = " "
var stream = null
var noColor = "-"


import "./fmt" for Fmt
var rand = Random.new()


// The Streamer abstract class defines how agent and world talk to each other.
// Move moves one sector in a random direction.
// If implemented in Wren, send and rec will be synchronous blocking operations.
// It retries on bumps and doesn't return until a forward command succeeds.
class Streamer {
// It expects a color event on a successful move and terminates if it doesn't
send(ch) {}
// get one.
var move = Fn.new {
rec() {}
while (true) {
// Randomness: 50/50 chance of turning or attempting move.
// For turns, equal chance of turning right or left.
var t = rand.int(4)
if (t == 0) {
stream.send(Ifc.cmdLeft)
Fiber.yield()
while (stream.rec() != Ifc.evStop) { Fiber.yield() }
continue
} else if (t == 1) {
stream.send(Ifc.cmdRight)
Fiber.yield()
while (stream.rec() != Ifc.evStop) { Fiber.yield() }
continue
}
stream.send(Ifc.cmdForward)
var bump = false
sectorColor = noColor
sectorBall = noColor
while (true) {
Fiber.yield()
var ev = stream.rec()
if (ev == Ifc.evBump) {
bump = true
} else if ([Ifc.evColorRed, Ifc.evColorGreen, Ifc.evColorYellow, Ifc.evColorBlue].contains(ev)) {
sectorColor = ev
} else if ([Ifc.evBallRed, Ifc.evBallGreen, Ifc.evBallYellow, Ifc.evBallBlue].contains(ev)) {
sectorBall = ev
} else if (ev == Ifc.evStop) {
break
}
}
if (bump) continue
if (sectorColor == noColor) Log.fatal("agent: expected color event after move")
return
}
}
}


// The agent and world send and receive single characters, out of the set of
// Get is only called when get is possible.
// constants defined here.
var get = Fn.new {
class Ifc {
stream.send(Ifc.cmdGet)
static handshake { "H" }
while (true) {
Fiber.yield()
static cmdForward { "^" }
var ch = stream.rec()
static cmdRight { ">" }
if (ch == Ifc.evStop) {
static cmdLeft { "<" }
// agent notes ball color, and that sector is now empty
static cmdGet { "@" }
agentBall = sectorBall
static cmdDrop { "!" }
sectorBall = noColor
static evGameOver { "+" }
return
static evStop { "." }
static evColorRed { "R" }
} else if (ch == Ifc.evNoBallInSector || ch == Ifc.evAgentFull) {
static evColorGreen { "G" }
Log.fatal("agent:expected get to succeed")
static evColorYellow { "Y" }
static evColorBlue { "B" }
static evBallRed { "r" }
static evBallGreen { "g" }
static evBallYellow { "y" }
static evBallBlue { "b" }
static evBump { "|" }
static evSectorFull { "S" }
static evAgentFull { "A" }
static evNoBallInSector { "s" }
static evNoBallInAgent { "a" }

// Human readable text for the above commands and events.
static init_() {
__hrText = {
handshake : "handshake",
cmdForward : "command forward",
cmdRight : "command turn right",
cmdLeft : "command turn left",
cmdGet : "command get",
cmdDrop : "command drop",
evGameOver : "event game over",
evStop : "event stop",
evColorRed : "event color red",
evColorGreen : "event color green",
evColorYellow : "event color yellow",
evColorBlue : "event color blue",
evBallRed : "event ball red",
evBallGreen : "event ball green",
evBallYellow : "event ball yellow",
evBallBlue : "event ball blue",
evBump : "event bump",
evSectorFull : "event sector full",
evAgentFull : "event agent full",
evNoBallInSector : "event no ball in sector",
evNoBallInAgent : "event no ball in agent"
}
}
}
}
}


static text { __hrText }
// FindMatching is only called when agent has a ball.
// FindMatching finds a sector where the color matches the ball the agent
// is holding and which does not already contain a matching ball.
// It does not necessarily find an empty matching sector.
var findMatching = Fn.new {
while (Char.lower(sectorColor) != agentBall || agentBall == sectorBall) move.call()
}
}


class Log {
// FindMisplaced wanders the maze looking for a ball on the wrong sector.
static prefix=(p) { __prefix = Fmt.swrite("$06d: ", p) }
var findMisplaced = Fn.new {
while (true) {
move.call()


static print(s) { System.print(__prefix + s) }
// get ball from current sector if meaningful
if ([Ifc.evBallRed, Ifc.evBallGreen, Ifc.evBallYellow, Ifc.evBallBlue].contains(sectorBall)) {
if (sectorBall != Char.lower(sectorColor)) return
}
}
}


static fatal(s) { Fiber.abort(s) }
// Drop is only called when the agent has a ball. Unlike get() however,
// drop() can be called whether the sector is empty or not. drop() means
// drop as soon as possible, so if the sector is full, drop() will wander
// at random looking for an empty sector.
var drop = Fn.new {
var gameOver = false
while (sectorBall != noColor) move.call()

// expected to work
stream.send(Ifc.cmdDrop)
while(true) {
Fiber.yield()
var ch = stream.rec()
if (ch == Ifc.evGameOver) {
gameOver = true
} else if (ch == Ifc.evStop) {
break
} else if (ch == Ifc.evNoBallInAgent || ch == Ifc.evSectorFull) {
Log.fatal("expected drop to succeed")
}
}
sectorBall = agentBall
agentBall = noColor
return gameOver
}
}


Ifc.init_()</syntaxhighlight>
var Agent = Fn.new { |s|
stream = s

// handshake
var hs = stream.rec()
if (hs != Ifc.handshake) Log.fatal("agent: that's no handshake")
stream.send(Ifc.handshake)
Fiber.yield()

// agent behavior main loop
var gameOver = false
while (!gameOver) {
findMisplaced.call()
get.call()
findMatching.call()
gameOver = drop.call()
}
}</lang>

Latest revision as of 11:22, 2 February 2024

Remote agent/Agent interface is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

In Remote agent, a component is described that marshals commands and events between a stream and a program that issues commands and processes the resulting events.


Task

Using the protocol definition described there, build this component in a fashion idiomatic and natural to your language.

C

See Remote agent/Simulation/C

Go

package ifc

// Streamer interface type defines how agent and world talk to each other.
//
// Send and Rec may be implemented as synchronous blocking operations.
// They can be considered perfectly reliable as there is no acknowledgement
// or timeout mechanism.
type Streamer interface {
    Send(byte)
    Rec() byte
}

// The agent and world send and receive single bytes, out of the set of
// constants defined here.
const Handshake = 'A'

const (
    CmdForward       = '^'
    CmdRight         = '>'
    CmdLeft          = '<'
    CmdGet           = '@'
    CmdDrop          = '!'
    EvGameOver       = '+'
    EvStop           = '.'
    EvColorRed       = 'R'
    EvColorGreen     = 'G'
    EvColorYellow    = 'Y'
    EvColorBlue      = 'B'
    EvBallRed        = 'r'
    EvBallGreen      = 'g'
    EvBallYellow     = 'y'
    EvBallBlue       = 'b'
    EvBump           = '|'
    EvSectorFull     = 'S'
    EvAgentFull      = 'A'
    EvNoBallInSector = 's'
    EvNoBallInAgent  = 'a'
)

Above is the interface package defining the abstract interface. Below is an example of a driver program that supplies a concrete implementation of the interface. This implementation uses Go channels to run a simulation in a single process. A different implementation might produce two executables that would run on different processes or different machines. It would use the same interface however.

package main

import (
    "log"
    "os"

    "ra/agent"
    "ra/world"
)

// Concrete type chanStreamer implements interface ifc.Streamer.
// Implementaion is with channels, a simple way to demonstrate concurrency.
type chanStreamer struct {
    name string
    in   <-chan byte
    out  chan<- byte
}

// Send satisfies ifc.Streamer.Send method.
func (c chanStreamer) Send(b byte) {
    log.Print(c.name, " sends ", text[b])
    c.out <- b
}

// Rec satisfies ifc.Streamer.Rec method.
func (c chanStreamer) Rec() byte {
    b := <-c.in
    log.Print(c.name, " recieves ", text[b])
    return b
}

func main() {
    // Logging is done with the log package default logger, so this is
    // technically something that might be shared by the agent and the world.
    // Currently though, the agent doesn't do anything with the logger, only
    // the world does.
    log.SetFlags(0)
    log.SetOutput(os.Stdout)
    // Create channels for chanStreamer.  Only two channels are needed,
    // cmd going from agent to world, and ev going from world to agent.
    cmd := make(chan byte)
    ev := make(chan byte)
    // Instantiate chanStreamer for agent and start agent as a goroutine.
    // This allows it to run concurrently with the world.
    go agent.Agent(chanStreamer{"agent", ev, cmd})
    // World doesn't need another goroutine, it just takes over main at this
    // point.  The program terminates immediately when World returns.
    world.World(chanStreamer{"world", cmd, ev})
}

A separate file, but still part of the driver, contains human readable text for commands and events. Alternatively, this file could be included with the interface package for maintainability, but technically it is not a required part of the abstract interface used by the agent and world simulator.

package main

import "ra/ifc"

// Human readable text for commands and events.
var text = map[byte]string{
    ifc.CmdForward:       "command forward",
    ifc.CmdRight:         "command turn right",
    ifc.CmdLeft:          "command turn left",
    ifc.CmdGet:           "command get",
    ifc.CmdDrop:          "command drop",
    ifc.EvGameOver:       "event game over",
    ifc.EvStop:           "event stop",
    ifc.EvColorRed:       "event color red",
    ifc.EvColorGreen:     "event color green",
    ifc.EvColorYellow:    "event color yellow",
    ifc.EvColorBlue:      "event color blue",
    ifc.EvBallRed:        "event ball red",
    ifc.EvBallGreen:      "event ball green",
    ifc.EvBallYellow:     "event ball yellow",
    ifc.EvBallBlue:       "event ball blue",
    ifc.EvBump:           "event bump",
    ifc.EvSectorFull:     "event sector full",
    ifc.EvAgentFull:      "event agent full",
    ifc.EvNoBallInSector: "event no ball in sector",
    ifc.EvNoBallInAgent:  "event no ball in agent",
}


Julia

See Remote agent/Agent_interface/Julia

Phix

abstract interface

Modify this to include one of the two concrete implementations below (but not both!)

--
-- demo\rosetta\Remote_Agent_Interface.exw
-- =======================================
--
-- First, a handful of common constants, just to avoid the duplication:
global enum North, East, South, West
global constant dxy = {{0,-1},{1,0},{0,1},{-1,0}}

-- and some display/progress routines:
global procedure show_at(integer l,c, string msg, sequence args={})
    position(l,c)
    printf(1,msg,args)
end procedure

constant pause = false
global procedure show(string what, integer event, yoffset, fn)
    if fn then
        {sequence {board, balls}, integer {x, y, d, ball}} = fn()
        show_at(yoffset,1,sprintf("%s  -- x:%2d, y:%2d, ball:%c, d:%d  ",{what,x,y,ball,d}))
        balls[y+1,x] = "^>v<"[d]    
        for i=1 to length(board) do
            show_at(i+yoffset,1,board[i])
            show_at(i+yoffset,40,balls[i])
        end for
        if event then
            show_at(20,1,"(after "&event&" accepted by %s)",{what})
            if pause then {} = wait_key() end if
        end if
    end if
end procedure

global procedure die(string msg)
    -- (to avoid clobbering the above display)
    position(35,1)
    crash(msg)
end procedure

--
--  Only one of the following should be included, the other(s) commented out:
--
include Remote_Agent_Interface_Direct.e
--include Remote_Agent_Interface_IPC.e
--include Remote_Agent_Interface_Xxx.e -- (no such file, see below)
--
-- each of the above must implement
--
--  global procedure register_world(integer accept_command, get_event, get_world=0)
--  global procedure register_agent(integer get_command, accept_event, get_agent=0)
--
--  The four mandatory and two optional routines to be provided are described below.
--  Note that Remote_Agent_Interface_Direct returns control immediately,
--  to one, whereas Remote_Agent_Interface_IPC does not, see below.
--
-- Remote_Agent_Interface_Direct.e includes both
-- Remote_Agent_Simulator.exw and
-- Remote_Agent_Agent_Logic.exw, and operates as a fully complete and
--  standalone program whichever of the three .exw files are invoked.
--  (Running >1 of them effectively runs the same program >1 times.)
--
-- Remote_Agent_Interface_IPC.e includes neither, and instead both of
-- Remote_Agent_Simulator.exw and
-- Remote_Agent_Agent_Logic.exw must be run separately.
--  (Running Remote_Agent_Interface.exw does nothing in that case.)
--
-- The idea is that further Remote_Agent_Interface_Xxx.e could be written
--  to implement pipes, sockets, files, etc. and nothing in either of the
--  other two .exw would need changing for them to work like IPC.e does.
--
-- There would of course be no problem with world/agent including those
--  files directly, as long as they are kept in sync, but of course that
--  way would lose the "no modification whatsoever to switch" aspect.
--
-- The Direct version checks whether both register_world and register_client
--  have been invoked before starting a single event loop, otherwise returns 
--  control immediately, effectively terminating one of world/client, whereas 
--  the IPC way sets up separate event loops in/for both register_world and 
--  register_client, and only returns control on game over (if even that).
--
-- Both world/client must operate reactively and maintain any internal state
--  that requires. For instance when the server receives a "forward" command 
--  it cannot send color; send ball; send stop; but must instead rely on 
--  being later asked for those events, and deliver them in order.
--
-- Both get_event() and get_command() must be a parameterless function that
--  returns a single byte, and both accept_command() and accept_event() must
--  be a procedure that accepts a single byte. As you should expect you use
--  () to invoke them, and omit the () when just passing their ids about.
--  The get_xxxx() routines, if provided, should return the current state
--  in a suitable format for use as per fn() in show() above.
--

Interface_Direct

This implementation of the above interface is a single standalone program

--
-- demo\rosetta\Remote_Agent_Interface_Direct.e
-- ============================================
--
include Remote_Agent_Simulator.exw
include Remote_Agent_Agent_Logic.exw

integer accept_command=0, get_event, get_world,
        get_command=0, accept_event, get_agent

procedure main_event_loop()
    integer handshake = get_event()
    if handshake!='A' then ?9/0 end if
    show_at(1,65,"server handshake recieved")
    handshake = get_command()
    if handshake!='A' then ?9/0 end if
    show_at(2,65,"agent handshake recieved")
    while true do
        integer command = get_command()
        show("agent",0,22,get_agent)
        accept_command(command) 
        show("world",command,1,get_world)
        while true do
            integer event = get_event()
            if event='+' then
                -- game over
                show("world",0,1,get_world)
                show("agent",'.',22,get_agent)
                show_at(5,66,"game over")
                {} = wait_key()
                return
            end if
            accept_event(event)
            show("world",0,1,get_world)
            show("agent",event,22,get_agent)
            if event='.' then exit end if   -- stop
        end while
    end while
end procedure

global procedure register_world(integer _accept_command, _get_event, _get_world=NULL)
    {accept_command, get_event, get_world} = {_accept_command, _get_event, _get_world}
    if get_command!=0 then main_event_loop() end if
end procedure

global procedure register_agent(integer _get_command, _accept_event, _get_agent=NULL)
    {get_command, accept_event, get_agent} = {_get_command, _accept_event, _get_agent}
    if accept_command!=0 then main_event_loop() end if
end procedure

Interface_IPC

This implementation of the above interface lets two separate programs communicate using ipc

--
-- demo\rosetta\Remote_Agent_Interface_IPC.e
-- =========================================
--
include builtins\ipc.e

-- The shared memory contains two bytes:
--  byte[1] (command) can only be set by the agent and cleared by the world,
--  byte[2] ( event ) can only be set by the world and cleared by the agent.

constant ra = "remote_agent"
atom pSharedMem = sm_open(ra)
if pSharedMem=SM_OPEN_FAIL then
    pSharedMem = sm_create(ra,2)
    if pSharedMem<=0 then
        crash("unable to create shared memory")
    end if
    poke2(pSharedMem,0) -- initialised to zero bytes
end if

procedure send_command_via_ipc(integer command)
    while peek(pSharedMem)!=0 do
        sleep(0.1)
    end while
    poke(pSharedMem,command)
end procedure

function get_command_from_ipc()
    integer command
    while true do
        command = peek(pSharedMem)
        if command!=0 then exit end if
        sleep(0.1)
    end while
    poke(pSharedMem,0)
    return command
end function

procedure send_event_via_ipc(integer event)
    while peek(pSharedMem+1)!=0 do
        sleep(0.1)
    end while
    poke(pSharedMem+1,event)
end procedure

function get_event_from_ipc()
    integer event
    while true do
        event = peek(pSharedMem+1)
        if event!=0 then exit end if
        sleep(0.1)
    end while
    poke(pSharedMem+1,0)
    return event
end function

procedure game_over()
    show_at(32,62,"game over")
    {} = wait_key()
end procedure

global procedure register_world(integer accept_command, get_event, get_world=0)
    integer event = get_event()
    if event!='A' then die("rw1") end if
    send_event_via_ipc(event)
    integer command = get_command_from_ipc()
    if command!='A' then die("rw2") end if
    show_at(1,60,"server handshake recieved")
    -- server loop:
    while true do
        command = get_command_from_ipc()
        if command='+' then
            game_over()
            return
        end if
        accept_command(command) 
        show("world",command,1,get_world)
        while true do
            event = get_event()
            send_event_via_ipc(event)
            if event='.' then exit end if   -- stop
        end while
    end while
end procedure

global procedure register_agent(integer get_command, accept_event, get_agent=0)
    integer command = get_command()
    if command!='A' then die("ra1") end if
    send_command_via_ipc(command) 
    integer event = get_event_from_ipc()
    if event!='A' then die("ra2") end if
    show_at(2,60,"agent handshake recieved")
    -- agent loop:
    while true do
        command = get_command()
        send_command_via_ipc(command) 
        while true do
            event = get_event_from_ipc()
            if event='+' then
                send_command_via_ipc(event) 
                show("agent",'.',22,get_agent)
                game_over()
                return
            end if
            accept_event(event)
            show("agent",event,22,get_agent)
            if event='.' then exit end if   -- stop
        end while
    end while
end procedure

PicoLisp

The interface logic for the PicoLisp solution is directly integrated into the client Remote agent/Agent logic#PicoLisp.

Tcl

Works with: Tcl version 8.6
package require Tcl 8.6

oo::class create AgentAPI {
    variable sock events sectorColor ballColor
    constructor {host port} {
	set sock [socket $host $port]
	fconfigure $sock -buffering none -translation binary -encoding ascii \
	    -blocking 0
	# Hack to allow things to work in 8.6b1 and 8.6b2
        if {![llength [info commands yieldto]]} {
	    interp alias {} yieldto {} ::tcl::unsupported::yieldTo
	}
	coroutine ReaderCoroutine my ReadLoop
    }
    destructor {
	if {[llength [info command ReaderCoroutine]]} {
	    rename ReaderCoroutine {}
	}
	if {[llength [info command AgentCoroutine]]} {
	    rename AgentCoroutine {}
	}
	if {$sock ne ""} {
	    catch {close $sock}
	}
    }
    method Log message {
    }

    # Commands
    method ForwardStep {} {
	my Log "action: forward"
	puts -nonewline $sock "^"
	my ProcessEvents [yield]
    }
    method TurnRight {} {
	my Log "action: turn right"
	puts -nonewline $sock ">"
	my ProcessEvents [yield]
    }
    method TurnLeft {} {
	my Log "action: turn left"
	puts -nonewline $sock "<"
	my ProcessEvents [yield]
    }
    method GetBall {} {
	my Log "action: get ball"
	puts -nonewline $sock "@"
	my ProcessEvents [yield]
    }
    method DropBall {} {
	my Log "action: drop ball"
	puts -nonewline $sock "!"
	my ProcessEvents [yield]
    }
    method ProcessEvents {events} {
	set sectorColor {}
	set ballColor {}
	set err {}
	set done 0
	foreach e $events {
	    my Log "event: $e"
	    switch [lindex $e 0] {
		sector {set sectorColor [lindex $e 1]}
		ball {set ballColor [lindex $e 1]}
		error {set err [lindex $e 1]}
		gameOver {set done 1}
	    }
	}
	if {$err ne ""} {throw $err "can't do that: $err"}
	return $done
    }

    # Event demux
    method ReadLoop {} {
	# Init handshake
	fileevent $sock readable [info coroutine]
	while 1 {
	    yield
	    if {[read $sock 1] eq "A"} break
	}
	puts -nonewline $sock "A"
	# Main loop; agent logic is in coroutine
	try {
	    coroutine AgentCoroutine my Behavior
	    while 1 {
		yield
		set ch [read $sock 1]
		switch $ch {
		    "." {
			# Stop - end of events from move
			set e $events
			set events {}
			yieldto AgentCoroutine $e
			if {"gameOver" in $e} break
		    }
		    "+" {lappend events gameOver}
		    "R" {lappend events {sector red}}
		    "G" {lappend events {sector green}}
		    "Y" {lappend events {sector yellow}}
		    "B" {lappend events {sector blue}}
		    "r" {lappend events {ball red}}
		    "g" {lappend events {ball green}}
		    "y" {lappend events {ball yellow}}
		    "b" {lappend events {ball blue}}
		    "|" {lappend events {error bumpedWall}}
		    "S" {lappend events {error sectorFull}}
		    "A" {lappend events {error agentFull}}
		    "s" {lappend events {error sectorEmpty}}
		    "a" {lappend events {error agentEmpty}}
		}
	    }
	} finally {
	    close $sock
	    set sock ""
	}
    }

    method Behavior {} {
	error "method not implemented"
    }
}

# Export as package
package provide RC::RemoteAgent 1

Wren

Translation of: Go
Library: Wren-fmt

Unlike the Go entry, I've placed the 'human readable text' code inside the 'ifc' module and have also placed a basic logging facility in there too as its called by both the 'world' and 'agent' modules. It always prints to stdout.

I've changed the 'handshake' symbol from "A" to "H" to distinguish it from 'evAgentFull'.

The 'driver code' has been moved to the Remote_agent/Simulation task.

/* ifc.wren */

import "./fmt" for Fmt

// The Streamer abstract class defines how agent and world talk to each other.
// If implemented in Wren, send and rec will be synchronous blocking operations.
class Streamer {
    send(ch) {}
    rec()    {}
}

// The agent and world send and receive single characters, out of the set of
// constants defined here.
class Ifc {
    static handshake        { "H" }
    static cmdForward       { "^" }
    static cmdRight         { ">" }
    static cmdLeft          { "<" }
    static cmdGet           { "@" }
    static cmdDrop          { "!" }
    static evGameOver       { "+" }
    static evStop           { "." }
    static evColorRed       { "R" }
    static evColorGreen     { "G" }
    static evColorYellow    { "Y" }
    static evColorBlue      { "B" }
    static evBallRed        { "r" }
    static evBallGreen      { "g" }
    static evBallYellow     { "y" }
    static evBallBlue       { "b" }
    static evBump           { "|" }
    static evSectorFull     { "S" }
    static evAgentFull      { "A" }
    static evNoBallInSector { "s" }
    static evNoBallInAgent  { "a" }

    // Human readable text for the above commands and events.
    static init_() {
        __hrText = {
            handshake        : "handshake",
            cmdForward       : "command forward",
            cmdRight         : "command turn right",
            cmdLeft          : "command turn left",
            cmdGet           : "command get",
            cmdDrop          : "command drop",
            evGameOver       : "event game over",
            evStop           : "event stop",
            evColorRed       : "event color red",
            evColorGreen     : "event color green",
            evColorYellow    : "event color yellow",
            evColorBlue      : "event color blue",
            evBallRed        : "event ball red",
            evBallGreen      : "event ball green",
            evBallYellow     : "event ball yellow",
            evBallBlue       : "event ball blue",
            evBump           : "event bump",
            evSectorFull     : "event sector full",
            evAgentFull      : "event agent full",
            evNoBallInSector : "event no ball in sector",
            evNoBallInAgent  : "event no ball in agent"
        }
    }

    static text { __hrText }
}

class Log {
    static prefix=(p) { __prefix = Fmt.swrite("$06d: ", p) }

    static print(s) { System.print(__prefix + s) }

    static fatal(s) { Fiber.abort(s) }
}

Ifc.init_()