8.14
1.3.3.4 Engine🔗
The engine performs the core game actions.
It uses a sequence, a board, and a table of places.
The sequence provides new ids for adding entities.
The board manages the size of the game area.
The places table keeps track of all the entities.
| (struct engine (sequence board places)) |
| (define (make-engine width height) |
| (engine (make-sequence) (board width height) (make-places))) |
A location is
available when it is
valid and there is no entity at that location.
test-engine is a macro to set up test data in an engine: source at testing.rkt
| (module+ test |
| (test-engine |
| ((size 3 2) (block 1 1)) |
| (check-false (available? engine (location 1 1))) |
| (check-true (available? engine (location 2 1))) |
| (check-false (available? engine (location 3 1))))) |
| (define (available? engine location) |
| (and (is-valid? (engine-board engine) location) |
| (not (entity-at (engine-places engine) location)))) |
The engine can add an entity.
| (module+ test |
| (let* ([engine (make-engine 9 10)] |
| [block (add-entity engine type-block (location 1 2))]) |
| (check-equal? (entity-type block) type-block) |
| (check-equal? (occupant-by-id (engine-places engine) (entity-id block)) |
| (occupant block (location 1 2))))) |
The location must be available.
| (module+ test |
| (check-false (add-entity (make-engine 10 9) type-bot (location -1 2)))) |
Each entity has a unique id.
| (module+ test |
| (let ([engine (make-engine 9 10)]) |
| (check-not-equal? (entity-id (add-entity engine type-bot (location 3 4))) |
| (entity-id (add-entity engine type-bot (location 5 6)))))) |
The new entity is returned, if successful.
Otherwise #f is returned.
| (define (add-entity engine type location) |
| (and (available? engine location) |
| (let ([new-entity (entity ((engine-sequence engine)) type)]) |
| (place-entity (engine-places engine) new-entity location) |
| new-entity))) |
The engine can move an entity to a new location.
The destination of the move must be adjacent and available.
| (module+ test |
| (test-engine |
| ((size 9 10) (bot 5 6)) |
| (check-not-false (move-entity engine bot-id (location 5 7))) |
| (check-equal? (place-by-id (engine-places engine) bot-id) |
| (location 5 7)))) |
Otherwise, the entity remains in its original location.
| (module+ test |
| (test-engine |
| ((size 11 10) (bot 9 9) (block 8 9)) |
| (check-false (move-entity engine bot-id (location 9 10)) "invalid location") |
| (check-false (move-entity engine bot-id (location 8 9)) "not available") |
| (check-false (move-entity engine bot-id (location 8 8)) "not adjacent") |
| (check-equal? (place-by-id (engine-places engine) bot-id) |
| (location 9 9)))) |
The result is not false if successful, otherwise it is #f.
| (define (move-entity engine id new-location) |
| (let ([bot (occupant-by-id (engine-places engine) id)]) |
| (and (adjacent? new-location (occupant-location bot)) |
| (available? engine new-location) |
| (place-entity (engine-places engine) |
| (occupant-entity bot) new-location)))) |
The engine can take an entity as cargo.
| (module+ test |
| (test-engine |
| ((size 3 4) (bot 1 1) (block 2 1)) |
| (check-not-false (take-entity engine bot-id block-id)) |
| (check-equal? (place-by-id (engine-places engine) block-id) |
| bot-id))) |
The entity being taken is placed in the bot, as cargo.
The result is not false if successful, otherwise it is #f.
| (define (take-entity engine id cargo-id) |
| (let ([cargo (occupant-by-id (engine-places engine) cargo-id)]) |
| (and cargo |
| (place-entity (engine-places engine) (occupant-entity cargo) id)))) |
The engine can drop an entity that is the cargo for a bot.
The destination of the drop must be adjacent and available.
| (module+ test |
| (test-engine |
| ((size 3 4) (block 2 1) (bot 1 1)) |
| (take-entity engine (entity-id bot) block-id) |
| (check-not-false (drop-entity engine bot-id (location 1 2))) |
| (check-equal? (place-by-id (engine-places engine) block-id) (location 1 2)))) |
Otherwise, the entity remains as cargo.
| (module+ test |
| (test-engine |
| ((size 3 4) (block1 2 1) (bot 1 1) (block2 0 1)) |
| (take-entity engine bot-id block1-id) |
| (check-false (drop-entity engine bot-id (location 3 1)) "invalid") |
| (check-false (drop-entity engine bot-id (location 0 1)) "not available") |
| (check-false (drop-entity engine bot-id (location 2 2)) "not adjacent") |
| (check-equal? (entity-at (engine-places engine) bot-id) block1))) |
The entity being dropped is placed at a location.
The result is not false if successful, otherwise it is #f.
| (define (drop-entity engine id drop-location) |
| (let ([bot (occupant-by-id (engine-places engine) id)]) |
| (and (adjacent? drop-location (occupant-location bot)) |
| (available? engine drop-location) |
| (place-entity (engine-places engine) |
| (entity-at (engine-places engine) id) |
| drop-location)))) |
The engine can transfer an entity from a bot to a base.
| (module+ test |
| (test-engine |
| ((size 3 4) (bot 1 1) (base 1 2) (block 2 1)) |
| (take-entity engine bot-id block-id) |
| (transfer-entity engine bot-id base-id) |
| (check-equal? (place-by-id (engine-places engine) block-id) base-id))) |
The bot must be adjacent to the base.
| (module+ test |
| (test-engine |
| ((size 3 4) (bot 1 1) (base 2 2) (block 2 1)) |
| (take-entity engine bot-id block-id) |
| (check-false (transfer-entity engine bot-id base-id)))) |
The entity being transferred is removed from the bot’s cargo and added to the base’s cargo.
The result is not false if successful, otherwise it is #f.
| (define (transfer-entity engine from-id to-id) |
| (let ([from-entity (occupant-by-id (engine-places engine) from-id)] |
| [to-entity (occupant-by-id (engine-places engine) to-id)]) |
| (and |
| (adjacent? (occupant-location from-entity) (occupant-location to-entity)) |
| (place-entity (engine-places engine) |
| (entity-at (engine-places engine) from-id) |
| to-id)))) |
Neighbors of a location are all the nearby entities.
| (module+ test |
| (test-engine |
| ((size 4 3) (block1 2 2) (block2 3 1)) |
| (let ([neighbors (neighbors engine (location 1 1))]) |
| (check-equal? (length neighbors) 1) |
| (check-equal? (occupant-location (first neighbors)) (location 2 2))))) |
Neighbors include edges.
| (module+ test |
| (test-engine |
| ((size 3 4)) |
| (let ([neighbors (neighbors engine (location 0 1))]) |
| (check-equal? (length neighbors) 1) |
| (check-equal? (occupant-type (first neighbors)) type-edge) |
| (check-equal? (occupant-location (first neighbors)) (location -1 1))))) |
| (define (neighbors engine bot-location) |
| (define (nearby entity location) |
| (and (nearby? location bot-location) |
| (occupant entity location))) |
| (append |
| (edges (engine-board engine) bot-location) |
| (filter-map-occupants (engine-places engine) nearby))) |
The engine provides entity info to be returned to the client.
| (module+ test |
| (test-engine |
| ((size 5 4) (bot1 2 2) (block1 2 3) (block2 1 1)) |
| (take-entity engine bot1-id block1-id) |
| (let-values ([(bot-occupant cargo neighbors) (entity-info engine bot1-id)]) |
| (check-equal? bot-occupant (occupant bot1 (location 2 2))) |
| (check-equal? cargo block1) |
| (check-equal? neighbors (list (occupant block2 (location 1 1))))))) |
The bot, its cargo, and its neighbors are retrieved from the places table.
| (define (entity-info engine entity-id) |
| (let ([occupant (occupant-by-id (engine-places engine) entity-id)]) |
| (values |
| occupant |
| (entity-at (engine-places engine) entity-id) |
| (neighbors engine (occupant-location occupant))))) |