Atlantis TDD (1) Service Provider and Interfaces

The first thing I’m going to need is a service provider that serves the interface to the game. I’m a little bit spoiled by testing in PHP, where it’s easy to use an associative array to build an interface on the fly, but I obviously won’t get this here. Here’s what my interface roughly looks like:

typedef struct iunit {
  struct unit * (*create)(void);
  void (*destroy)(struct unit *);
  struct unit * (*get)(int);

  int (*get_uid)(const struct unit *);
  struct region * (*get_region)(const struct unit *);
  void (*set_region)(struct unit *, struct region *);
  ... /* more of the same */
} iunit;

/* similar struct for iregion, iship, ibuilding TBD */

typedef struct igame {
  struct iunit * units;
  struct iregion * regions;

  int max_directions;

  void (*reset)(void);
  void * (*get_regions)(struct icursor **);
  ... /* more of the same */
} igame;

That’s a lot of function pointers! As I said in my last post, the idea is to write code that is independent of implementation, so at no point can I use the actual unit structure that’s in Atlantis, or even access u->no directly. Some other game might have an entirely different way of storing ids, after all. Instead, I will be using svc.units->get_uid(u), which is more to type, but easy to plug into with an implementation-dependent function. You can see the rest of the interface classes on github, if you are curious.

I’m pretty pleased with the icursor interface. The global list of the game’s regions in Atlantis is a next-pointer chained linked list. In Eressea, it is an unrolled linked list, and I assume that in A5, it is a std::list. Or maybe it’s a hashtable? I will be iterating over all regions a lot in the game logic, and similar issues will arise for all units in a region, etc. So here’s the icursor interface:

typedef struct icursor {
  void (*destroy)(void * cursor);
  int (*get)(void * cursor, int n, void * results[]);
  int (*advance)(void ** cursor, int n);
} icursor;

A function like svc.get_regions returns a void * typed data pointer (the cursor), and an icursor interface that can be used to iterate the particular type of container it represents. You can either get one or more elements from a cursor, or advance it. When you’re done, release the cursor by calling destroy. Here’s an example:

  icursor *icursor;
  void *cur = svc.get_regions(&icursor);
  region *r;
  while (icur->get(cur, 1, &r)) {
    printf("region %d,%dn", r->x, r->y);
    icur->advance(&cur, 1);
  }
  icur->destroy(cur);

That’s the first game logic I’ve written since I started! If you think I’m off to a good start and will be writing that movement code next, think again: It’s time to write tests first!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.