1.3 Server
1.3.1 Sequence
1.3.2 Cargos
1.3.3 Grid
1.3.4 Engine
1.3.5 Setup
1.3.6 Agent
1.3.7 Interval
1.3.8 Dispatcher
1.3.9 TCP Server
8.14
1.3.3 Grid🔗

Source code at grid.rkt

The grid represents the 2D board where all the entities interact in the game. Its size is the number of rows and columns. The entities are stored in a hash table. The key is the entity id, the value is the entity.

(struct grid (size hash))
(define (make-grid size) (grid size (make-hash)))

The grid can place an entity in the table and retrieve the entity by id.

(test-case:
 "place and retrieve"
 (let ([grid (make-grid 5)]
       [block (entity 101 type-block (location 1 2))])
   (place-entity grid block)
   (check-equal? (entity-by-id grid 101) block)))

This is done with basic hash table functions.

(define (entity-by-id grid id) (hash-ref (grid-hash grid) id #f))

(define (place-entity grid entity)
  (hash-set! (grid-hash grid) (entity-id entity) entity))

The grid can also remove an entity from the table.

(test-case:
 "remove"
 (let ([grid (make-grid 5)])
   (place-entity grid (entity 101 type-block (location 1 2)))
   (remove-entity grid 101)
   (check-false (entity-by-id grid 101))))
(define (remove-entity grid id)
  (hash-remove! (grid-hash grid) id))

Entities can be retrieved from the grid by location. We can find an entity at at a location, and get any the entities nearby a location.

(test-case:
 "retrieve by location"
 (let ([grid (make-grid 5)]
       [block1 (entity 101 type-block (location 1 2))]
       [block2 (entity 102 type-block (location 3 3))])
   (place-entity grid block1)
   (place-entity grid block2)
   (place-entity grid (entity 103 type-block (location 2 4)))
   (check-equal? (entity-at grid (location 1 2)) block1)
   (check-equal? (entities-nearby grid (location 2 2)) (list block1 block2))))

The hash-values function returns a list of the values in the table. We can then find a single instance or filter the list.

(define (entity-at grid location)
  (~>> grid grid-hash hash-values
       (findf (λ (entity) (equal? (entity-location entity) location)))))
(define (entities-nearby grid location)
  (~>> grid grid-hash hash-values
       (filter (λ (other) (nearby? location (entity-location other))))))

A location is valid when it is part of the grid.

(test-case:
 "valid locations"
 (check-true (is-valid? (make-grid 1) (location 0 0)))
 (check-true (is-valid? (make-grid 10) (location 9 9)))
 (check-false (is-valid? (make-grid 1) (location 0 -1)))
 (check-false (is-valid? (make-grid 1) (location -1 0)))
 (check-false (is-valid? (make-grid 10) (location 10 9)))
 (check-false (is-valid? (make-grid 10) (location 9 10))))

The location’s x and y coordinates are checked using the size of the grid.

(define (is-valid? grid location)
  (define (in-range? n) (and (>= n 0) (< n (grid-size grid))))
  (and (in-range? (location-x location))
       (in-range? (location-y location))))

A location is available when it is valid and there is no entity at that location.

(test-case:
 "available locations"
 (let ([grid (make-grid 3)])
   (place-entity grid (entity 101 type-block (location 1 1)))
   (check-false (is-available? grid (location 1 1)))
   (check-true (is-available? grid (location 2 1)))
   (check-false (is-available? grid (location 3 1)))))
(define (is-available? grid location)
  (and (is-valid? grid location)
       (not (entity-at grid location))))

Edges are entities outside the grid. They are adjacent to locations at the boundaries of the grid.

(test-case:
 "no edges in middle"
 (check-equal? (length (edges (make-grid 3) (location 1 1))) 0))
(test-case:
 "edges at limits"
 (check-equal? (length (edges (make-grid 1) (location 0 0))) 4))

We check in all directions from the given location and return an edge if the adjacent location is not valid.

(define (edges grid location)
  (for/list ([adjacent (all-directions location)]
             #:unless (is-valid? grid adjacent))
    (make-edge adjacent)))

Neighbors of a location are all the nearby entites, plus any edges.

(test-case:
 "neighbors are nearby"
 (let* ([grid (make-grid 4)])
   (place-entity grid (entity 101 type-block (location 2 2)))
   (place-entity grid (entity 102 type-block (location 3 1)))
   (let ([neighbors (neighbors grid (location 1 1))])
     (check-equal? (length neighbors) 1)
     (check-equal? (entity-location (first neighbors)) (location 2 2)))))
(test-case:
 "neighbors include edges"
 (let* ([grid (make-grid 3)]
        [neighbors (neighbors grid (location 0 1))])
     (check-equal? (length neighbors) 1)
     (check-equal? (entity-type (first neighbors)) type-edge)
     (check-equal? (entity-location (first neighbors)) (location -1 1))))
(define (neighbors grid location)
  (append
   (edges grid location)
   (entities-nearby grid location)))

The grid performs a procedure on each entity to map the entities for a game viewer.

(test-case:
 "map all"
 (let ([grid (make-grid 5)])
   (place-entity grid (entity 102 type-block (location 3 3)))
   (place-entity grid (entity 103 type-block (location 2 4)))
   (check-equal? (map-entities grid (λ (entity) (entity-id entity)))
                 '(102 103))))

This is another wrapper on a hash table function.

(define (map-entities grid procedure)
  (hash-map (grid-hash grid)
            (λ (_ entity) (procedure entity))))

The grid selects a random base location. The location must have all adjacent locations available.

(test-case:
 "random base"
 (let ([grid (make-grid 4)])
   (place-entity grid (entity 101 type-block (location 1 0)))
   (check-not-equal? (random-base grid) (location 1 1))))
(define (random-base grid)
  (let* ([top (sub1 (grid-size grid))]
         [location (location (random 1 top) (random 1 top))])
    (if (andmap (λ (x) (is-available? grid x))
                (all-directions location))
        location
        (random-base grid))))