-
Notifications
You must be signed in to change notification settings - Fork 1
MatchmakingPackage
Location: snipe/packages/matchmaking/
This package provides the user matchmaking server capability. Users are put into the queue and periodically checked against one another with the provided function. Multiple independent queues are supported with the limitation that a single user can only be in a single queue at any time.
You can include this package into the game server with:
override function initModulesGame()
{
loadModules([
snipe.packages.matchmaking.GameModule,
]);
}
You can include this package into the cache server with:
loadModules([
snipe.packages.matchmaking.CacheModule,
]);
Cache server configuration variables:
- "packages/matchmaking.time" - Timer delay in seconds between matchmaking checks. Equals 1 by default.
The Snipe core contains a full example of a tic-tac-toe game with games started through the user matchmaking. The example is located in "examples/tictactoe/" directory. Note that this example only supports matchmaking on a single game server thread. If you want meta-server wide matchmaking explained, you will need to take a look into the "examples/tictactoeRoom/" example instead. Also, in some cases after the users are matched you can communicate the game state changes through the server-to-server notifications if that is feasible. That will most certainly not work for real-time games because of the amount of produced server-to-server traffic.
Let's take a closer look at the provided matchmaking example. The first thing we need to do is to declare the matchmaking user class. The package expects the "MatchmakingUserClass" class available, just like "ItemProtoClass" and "UserClass" used elsewhere in the core. The contents of the file are simple enough:
typedef MatchmakingUserClass = MatchmakingUser;
And the "MatchmakingUser.hx" contains the following declaration:
class MatchmakingUser
{
public var serverID: Int;
public var id: Int;
public var rating: Int;
public var __addedTime: Float;
public function new()
{
serverID = 0;
id = 0;
rating = 0;
__addedTime = 0;
}
}
The package requires only the "id" and "__addedTime" fields. The rest will be needed for your custom check and success handling functions. These class and type declarations will need to be accessible by both the cache server and game server parts so you will have to put them into the project root directory or make a symbolic link where needed.
The next thing to do is to register the matchmaking queue type in the cache server module:
class MatchmakingModuleCache extends ModuleCache<CacheServer>
{
public function new(s: CacheServer)
{
super(s);
name = 'matchmaking';
var module: CacheModule = server.getModule('packages/matchmaking');
module.registerType({
id: 'basic',
check: check,
successPost: successPost
});
}
// method to call on matchmaking check
function check(u1: MatchmakingUser, u2: MatchmakingUser): Bool
{
if (Math.abs(u1.rating - u2.rating) >= 10)
return false;
// for the purposes of example simplicity we don't allow users on
// different game servers in one game
if (u1.serverID != u2.serverID)
return false;
return true;
}
// method to call on matchmaking success
function successPost(u1: MatchmakingUser, u2: MatchmakingUser)
{
server.log('match', 'success ' + u1 + ',' + u2);
var s = server.getClient(u1.serverID);
s.notify({
_type: 'game.start',
userID1: u1.id,
userID2: u2.id
});
}
}
Everything is pretty straightforward here. We register the queue type with the "check()" function to check for user pair matches and "successPost()" function to call on successful match. In this case we notify the game server to start the game.
The matchmaking package runs a cache server timer. Every second (the period of time is controlled by the configuration variable) all users are checked against each other. If the check function returns true, both users are removed from the queue and the success handler is called.
Now for the game server part. We need to handle client requests to enter and leave the queue and we need the game start notification handler:
class MatchmakingModule extends Module<TestClient, ServerTest>
{
public function new(srv: ServerTest)
{
super(srv);
name = "matchmaking";
}
public override function call(c: TestClient, type: String, params: Params): Dynamic
{
var response = null;
if (type == "matchmaking.add")
response = add(c, params);
else if (type == "matchmaking.remove")
response = remove(c, params);
return response;
}
// CALL: add user to matchmaking list
function add(c: TestClient, params: Params)
{
var user = new MatchmakingUser();
user.id = c.id;
user.rating = c.rating;
user.serverID = server.slaveID;
var module: GameModule = server.getModule('packages/matchmaking');
module.add('basic', user);
return { errorCode: 'ok' };
}
// CALL: remove user from matchmaking list
function remove(c: TestClient, params: Params)
{
var module: GameModule = server.getModule('packages/matchmaking');
module.remove('basic', c.id);
return { errorCode: 'ok' };
}
}
The matchmaking requests module is very simple. Note that in the production code we would at least need to mark the clients on entering the queue to disable parts of server functionality and to prevent them from entering multiple queues, for example, by using the client state variable stored in the client instance.
The game start notification in the game module is also straightforward:
class GameModule extends Module<TestClient, ServerTest>
{
// [game module contents omitted for brevity]
// NOTIFY: start a new game
function start(msg: { userID1: Int, userID2: Int })
{
var c1 = server.getClient(msg.userID1, true);
var c2 = server.getClient(msg.userID2, true);
// one of clients disconnected
if (c1 == null || c2 == null)
return;
// one of clients is in wrong state
if (c1.state != '' || c2.state != '')
throw 'game.start(): wrong state';
var g: _Game = {
id: _lastGameID++,
field: [ [ 0, 0, 0 ], [ 0, 0, 0 ], [ 0, 0, 0 ] ],
userID1: c1.id,
userID2: c2.id,
userID1isX: true,
turn: false, // for testing simplicity, user 1 always starts first
}
_list.set(g.id, g);
c1.state = 'game';
c1.gameID = g.id;
c2.state = 'game';
c2.gameID = g.id;
// notify clients
c1.response('game.start', { errorCode: 'ok' });
c2.response('game.start', { errorCode: 'ok' });
}
// [game module contents omitted for brevity]
}
On receiving the game start notification the game server will initialize the game state and put it into a list of currently running games for later access by the clients.
That is all for the basic matchmaking. If you need to have more than two users matched, you can use the provided Extended Matchmaking package that is fully customizable.