Biot World!!
Secretly, Bryan and I are starting work on ‘Biot World’, a start at the joint project our Slack has been talking about.1
Yesterday, Bryan Beecham and I started pairing on a project we’re calling BiotWorld. Here’s a short first-draft description of the project:
BiotWorld is a client-server game. The world (server) provided is your typical square grid world, details later. The world has a number of different kinds of objects in it, treasures, tricks, traps, and your biot. A “biot”, short for “Robiot”, a client, is a device or creature for exploring the world, which is hostile to human life but has resources that humans want. Your mission is to field one or more biots, forage for resources, sell them at bases which export them back to earth. You can trade with other biots as well.
Your biot needs food and fuel. Its senses are weak: at a moderate distance around itself, it can determine that there is some thing, and the thing’s coordinates, but not what the thing is. At a closer distance, probably directly adjacent to the thing, your biot gets more information, but not much. Your biot can try to communicate with the thing, offering to buy or sell things. It can try to harvest the thing, which might be food or fuel or some valuable trade item.
It’s early days, so even this much is uncertain and to be figured out as we go.
BiotWorld is an example project. It will be offered to the world, and to a particular programming school, as an example of how we gray-beards would do something like this. We propose, if things work out, to invite other developers to create clients for the game, in a language and style of their choice, to help learners see how we do things.
And possibly, the project will itself serve as an example of how to create a significant program, not by specifying it in advance, but by evolving the understanding of what the program is while building the program.
Here’s what we might provide today to the student team if we gave them this assignment:
As a team, produce a game like the one described above. You must deliver a client-server implementation of the game with separate server and client code. Information as to how to set up your server will be forthcoming. Client and server must communicate using a JSON format which you will devise and document. At some point, with reasonable notice, new teams will be given your documentation and access to you, to write clients of their own. Your server must be written in [some language]. Your client may be written in [some language], or, with permission from your student guide, in a language of your choosing.
You are expected to use all the techniques you have learned so far, including small steps, working driven by tests, and so on. Your program will be inspected by our Gray Beard Committee and scored by their standards, with which you are familiar.
Now, of course, what you’re viewing here is the result of a few days of slack messaging and an hour or so of chat with our friends, and eighty minutes of development. So what we wind up with may not look much like what’s above. We’ll find out.
We have kicked the general idea around for quite a while, and we chatted for perhaps 15 minutes before we started, and, of course, as we paired. In total we spent about 80 minutes chatting and testing. The results so far are:
class TestCase:
def test_something(self):
assert True is True
def test_create_biot(self):
biot = Biot()
assert isinstance(biot, Biot)
def test_starting_location(self):
biot = Biot()
assert biot.location == Point(0, 0)
def test_biot_in_world(self):
biot = Biot()
world = BiotWorld()
info = world.add(biot)
point = info["location"]
biot.update(info)
assert biot.location == point
@pytest.mark.skip()
def test_create_biot_map(self):
map = BiotMap()
class Biot:
def __init__(self):
self.location = Point(0, 0)
def update(self, info):
self.location = info["location"]
class BiotMap:
pass
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
class Entities:
def __init__(self):
self.contents = {}
def place(self, biot, location):
self.contents[location] = biot
class BiotWorld:
def __init__(self):
self.biots = Entities()
def add(self, biot):
location = Point(10, 10)
self.biots.place(biot, location)
return {"location": location}
So that’s about 60 lines, a bit more, many of them blank. So our productivity is about 3/4 of a line of code per minute, for two programmers, or 3/8 of a line per minute each. There being about 400 productive minutes in a day, that comes to about 150 lines per day. If we knew how many lines this program would require, we could predict exactly when it will be done.
Excuse me, I went a little weird there for a moment. What I meant to point out was this:
In only three working tests, not counting the hookup test that verifies that True is True, we have created initial objects and protocol:
- The Biot object knows its location, a Point object knowing x and y. The Biot is either the client, or an element of a client, we’re not sure which.
- Some kind of Entities class, which, if you had been there for the discussion, you’d know is a collection indexed by location. I had in mind that it would be a thing that would implement “knowledge” of where certain things are. It may have been premature to introduce it, but we were about to introduce a dictionary and I pushed toward something more abstract.
- The BiotWorld object represents the server side of things, that knows where all the biots, treasures, tricks, and traps are in the world.
- The BiotMap, well, we don’t exactly know yet. We started that skipped test, then realized it was too big a bite and skipped it to make some smaller steps. But it will probably be an object that represents the map.
I suspect that there is overlap between the Entities idea and the BiotMap idea. Certainly none of these classes is set in stone, or even in sand. More like water at best.
However, notice that the client-server relationship is already vaguely present here;
def test_biot_in_world(self):
biot = Biot()
world = BiotWorld()
info = world.add(biot)
point = info["location"]
biot.update(info)
assert biot.location == point
Here, we begin on the client side and create a biot. We create a BiotWorld, which, as the thing grows, will be a connection on the client side to the server. Then the client sends add
to the world, which will be its initial connection action. The server world will return an info
, which the biot will use to update itself. The test saves the info
so that it can check the point created by the world against the point the biot saves.
This is a round-trip test between a client (the test right now) and the server (a local object, BiotWorld). But we have done it without a network, a client, or a server.
I freely grant that I pushed things in this direction, because I know that Bryan knows how to set up client-server python programs, so I am sure we can do it when we have to, and I believe that it would be a waste of time, and possibly actively harmful, to do it now. We will get to it in due time. So even before the idea came up, I was pushing against it. But I want to be clear that I was doing that on purpose.
Why am I so locked in on this? Well, because I’m sure that it will make our work easier and faster. On both sides, the objects should, in my view, have no knowledge that there is a client-server relationship going on. The biots should talk to the world as if it were part of the same program. The world should talk to the biots as if they were in the world with it. And on each side, the thing they talk to should be a “proxy” for the remote objects, entirely transparently.
At some point in my past, I was apparently frightened by a client-server setup that contaminated the domain objects, so I fear setting it up too soon. Someone said that everything in one’s software process is about fear, and this idea certainly is.
We’ll still come up with the necessary stringified format for messages between biot and world, probably JSON, because we hope to attract other clients in addition to our own. But—it seems to me—the Biot
should think it is talking with a World
object that has a standard Python object protocol, where you send it regular messages like info=world.add(self)
and get back a standard Python Info
object with a protocol that is useful to the Biot
. Behind the scenes, the world object will do all the messy work of converting objects back and forth from JSON, and, when its connected to an actual remote world, it’ll deal with the HTTP or whatever you kids call it that talks over the Internet.
I think this will work, and work well, and that it will be discernibly much easier and faster than if we were to set up and start working client-server right now.
Of all the things to watch in this series of articles, this is probably the most important one. If I’m right, things will go smoothly. If I’m wrong, we’ll get in serious trouble when we finally do the client-server thing.
We’ll find out … as we continue the adventures of Biot in BiotWorld!! (Cue theme music)
-
Spoiler: we have already tired of the made-up word “biot” and begun calling them “bots” a few articles from now. If you read “bots” for “biots” you’ll be ready for the shift, and perhaps less irritated by the strange word. ↩