All posts by Andrei

Bots as Fake Players — part 1

For multiplayer games, one of the biggest problems is bootstrapping. Even if someone discovers the game, dropping into an empty arena is not very fun gameplay.

There’s two different ways of approaching the same problem – get more real players, or improve the experience for when no real players are available.

If your game’s a hit, or you’re comfortable advertising as-is, then the first is usually the way to go. While developing – especially locally – that may not always be reasonable. I’ll be focusing on the later approach – how do you make a multiplayer game fun when no players are available? Well, let’s make our own players!

For a multiplayer game, the interface between client and server is (hopefully) very well defined.

In an ideal world, the client is purely a terminal – it renders the state that the server continuously feeds it. Trusting the client to maintain state has a lot of implications regarding cheating, so sidestepping that is usually preferred. If this is the case, the interface is the network – the client sends messages regarding their intent, and the server communicates the results of those actions.

Incidentally, there may be some issues with latency in this model – if your players are far enough away from the server it may take hundreds of milliseconds to see the results of their inputs. This is noticeable to the human eye.

So let’s say we wanted to create a very basic bot — one way of doing so is just taking your existing networking code and invoking it exactly as a client would. With this approach, you can have your bot logic actually react to the exact same data that the players would see, and send commands in exactly the same way a real player would. This means that theoretically the best bot would never do anything that a real player would not able to. In practice, humans do have a specific reaction time, so depending on the game a bot may have to be artificially limited in some way to approach human skill.

The other approach to bots is to inject their logic directly into the game code server-side. This is what I chose for this specific project, since it required less moving parts overall.

In my game loop, I have a function called ‘tick’ that the server makes sure is called once per logical frame of execution. All game logic is done through this function, to maintain fairness. When a player sends a game action, their intent is noted and associated with them, but the state does not actually get modified against the next tick. No matter how fast a player sends commands, they will be limited to the same speed as anyone else.

In order to introduce bots to the game, I defined a module with two functions – ‘newBot’ and ‘processBot’.


var unid = 0;
var memory = {};

exports.newBot = function(field) {
var id = "Bot" + (unid++);
memory[id] = {state: 'idle', fairness: Math.random()};
return {
nick: "PudgeBot" + unid,
id: id,
};
};

This function is called whenever the game decides it wishes to create a bot. I reused the same interface that a normal player would be using from the connection protocol I defined, except the bot response includes a specified ‘id’ value. (Normal connected users use the socket ID defined directly by Socket.io).

‘memory’ is a way to let bots keep track of some state for themselves – which player they’re targeting, where they want to be going, and what they want to do


// Returns an intent structure if it wishes to change what it's doing.
exports.processBot = function(vision, id, p, i) {...}

The second function I defined is invoked by the ‘tick’ method every frame for all bots. Some of the arguments are pretty straightforward – id is the bot id, p is the current player state, i is the current intent, but the ‘vision’ variable was something I needed to add in order to let a bot have a way to see what is going on.


// Object to be used for bots.
var vision = {
nearby: function(personId, pos, maxDistance) {...}
};

This function interacts with the rest of the game and returns a set of the nearest players to a given position. Coming from a Java day job, being able to pass function pointers around is very nice.

With a combination of a simple ‘process’ function and hooking the current game code to create bots when there aren’t enough players, this was enough to implement functionality that is almost as good as many players. I’ll go into detail on the actual behaviours that bots replicate in a later post.

Come try out the bots and play at http://ganks.io:7777/! (I have not set up an actual proxy yet.)

Rapid Iteration

There’s nothing like getting more eyes on something in order to motivate quicker progress.

Once you get something up and running, smaller incremental changes form a very nice feedback loop. Building a major feature is hard. Tweaking a small change that someone asked for is easy and gives immediate satisfaction.

And the sum total of a lot of small features may equal the major feature.

Multiplayer

Overdesigning is something I try to avoid all the time, but I still get caught in the trap quite a while.

There’s a lot of devops productivity tools out there. AWS codebuild/deploy/pipeline and GitHub are the ones that come to mind immediately but there’s plenty of industry tools out there as well.

That said, when prototyping, there’s something to the Gordian Knot.

When starting a project, there’s hundreds of things that can take up your time that really don’t provide any value. If you’re building something, let what you’re building guide what you should do next.

In my case, you can spend a few hours automating your build process and improving your devops – but for a small prototype that doesn’t even have any logic in it? Throw up a single host and download your code to it every once in a while.

And so, I managed to point myself at actually getting some tangible progress on the small game I’m building:

Network programming has sure gotten easier…

Thanks to Alvin’s guide I’ve started learning Node.js (using the Express framework and socket.io library).

Last I played around with networking was using the raw websock libraries in C about 10-15 years ago. That was a lot more difficult than it is now, where approximately 10 lines of Javascript server code and 5 lines of Javascript client code is all you need to be passing messages back and forth. This lets developers like me completely ignore the underlying networking layer and focus on building cool applications.

So with some small amount of code I’ve made green circles that can move around and aim. This is the first time in quite a long time that I’ve programmed purely for fun — I can see myself continuing to do so.

(Now trying to get the networking rules to open the port through my ISP, router, and Windows setup was not something I succeeded at. I might just spin up a small remote host for this purpose once I have more of a game around it.)

Starting some more code for fun

Haven’t done any games programming in a while, so I kicked off a new project using libgdx. It provides a bunch of utilities for making cross-platform games, and I especially like the JSON loading utilities and the Pixmap utilities for drawing.

Since I’m just doing this for fun, and I’m an awful artist, I started by playing around with some Link’s Awakening spritesheets to see if I can replicate a 13-year old game. Loading the image is trivial with libgdx:

Pixmap image = new Pixmap(Gdx.files.internal("filename.png"));

This gives you access to a Pixmap object that can be used to draw into other pixmap objects. Once you’re happy with your drawing you can write it to a Texture and draw it on the screen. I wrote some quick code to extract the individual tiles and drew some random art to the screen.

Random Tiles

Obviously not the most useful but fun to look at. Even the code calls out the uselessness of the method.


// Not a very useful method.
public Tile getRandom() {
Random random = new Random();
return get(random.nextInt(tileCols), random.nextInt(tileRows));
}

In order to do something useful with it, I needed to create maps from the tiles that I was using. I decided to add a layer of abstraction around the concept of a tile, so that it’s not just a graphic. So I broke it up into spritesheets – purely graphical objects – and tilesets – which contain gameplay semantics.

The JSON interfaces that libgdx provides allow files to be loaded with a small amount of code. I whipped up a quick tileset description and a loader for it to describe the various tiles that were available in the sprite sheet.


{
spritesheet: {
image: "overworld.png",
},
tiles: {
grass_ul: {
c: 0, r: 5,
passable: true,
},
// ...
house_roof_blue: {
c: 8, r: 2,
passable: false,
},
// ...

Once the sprite sheet was loaded, I created a map object that would be the actual method of interaction with the game, and created a loader for a test map that I made as well.


{
tileset: {
file: "overworld.tileset.json",
},
width: 10,
height: 8,
tiles: [
[tree_dr, grass_l, grass_h, grass_h, grass_h, grass_h, grass_h, grass_h, grass_h, grass],
// ...

After a bit of organization, and about an hour or so of work, I had something that looked a bit more pretty, if less chaotic.

House