1.3 The Code
1.3.3 Server
1.3.3.1 Sequence
1.3.3.2 Board
1.3.3.3 Places
1.3.3.4 Engine
1.3.3.5 Background
1.3.3.6 Draw
1.3.3.7 Setup
1.3.3.8 Agent
1.3.3.9 Interval
1.3.3.10 Dispatcher
1.3.3.11 TCP Server
8.14
1.3.3.8 Agent🔗

Source code at agent.rkt

An agent represents a body of code that interacts with the game. This may be a player client, or a game viewer client.

(struct agent ([type #:mutable] [score #:mutable] [bots #:mutable]))
(define players '())
(define (make-agent) (agent 'unassigned 0 '()))

When new bots are created for a player, we assign the bots to the agent. We can them find the agent that owns a bot.

(test-case:
 "assign bots"
 (let ([agent (make-agent)])
   (assign-bots! agent (list 101 102))
   (check-equal? 1 (find-agent (list (make-agent) agent) 102))
   (check-false (find-agent (list (make-agent) agent) 103))))
(define (assign-bots! agent bots)
  (set-agent-bots! agent bots))
(define (find-agent agents bot-id)
  (index-where agents
               (λ (agent) (member bot-id (agent-bots agent)))))

The first request sent to the agent assigns its agent type. A draw request means the agent is a game viewer. A hello request means the agent is a game player.

(test-case:
 "set type"
 (define (check-type request)
   (let ([agent (make-agent)])
     (assign-agent-type! agent request)
     (agent-type agent)))
 (check-equal? (check-type request-draw) 'viewer)
 (check-equal? (check-type request-hello) 'player))

If the agent type is unassigned, it means that the type hasn’t been set yet. Once set, the type doesn’t change.

(define (assign-agent-type! agent request)
  (when (equal? (agent-type agent) 'unassigned)
    (cond
      [(equal? request request-hello)
       (set-agent-type! agent 'player)
       (set! players (cons agent players))]
      [(equal? request request-draw) (set-agent-type! agent 'viewer)])))

An agent checks if a request is valid, based on the agent type.

(test-case:
 "valid request"
 (let ([agent (agent 'unassigned 0 '())])
   (check-true (request-is-valid? agent request-draw))
   (check-true (request-is-valid? agent request-hello))
   (check-false (request-is-valid? agent '(#f))))
 (let ([agent (agent 'viewer 0 '())])
   (check-true (request-is-valid? agent request-draw))
   (check-false (request-is-valid? agent request-hello))
   (check-false (request-is-valid? agent '(#f))))
 (let ([agent (agent 'player 0 '())])
   (check-false (request-is-valid? agent request-draw))
   (check-false (request-is-valid? agent request-hello))
   (check-true (request-is-valid? agent '(#f)))))

If the type hasn’t been set yet, a draw or hello request is valid. For a game viewer, only draw requests are OK. For a game player, each request must be a list of commands.

(define (request-is-valid? agent request)
  (let ([type (agent-type agent)])
    (cond
      [(equal? type 'unassigned)
       (or (equal? request request-hello) (equal? request request-draw))]
      [(equal? type 'viewer) (equal? request request-draw)]
      [(equal? type 'player) (list? request)])))

An agent matches a request by checking if the request is valid, and setting the agent type.

(test-case:
 "match request"
 (let ([agent (make-agent)])
   (check-false (match-request agent '(#f)))
   (check-equal? (agent-type agent) 'unassigned)
   (check-true (match-request agent request-draw))
   (check-equal? (agent-type agent) 'viewer)))

If the request is not valid, the type is not changed. The result of the valid check is returned.

(define (match-request agent request)
  (let ([valid? (request-is-valid? agent request)])
    (when valid? (assign-agent-type! agent request))
    valid?))

When certain player events occur, an agent can add to its score.

(test-case:
 "add to score"
 (let ([agent (agent 'player 0 '())])
   (add-to-score agent 3)
   (check-equal? (agent-score agent) 3)))
(define (add-to-score agent amount)
  (set-agent-score! agent (+ amount (agent-score agent))))