Skip to content

Instantly share code, notes, and snippets.

@novabyte
Last active September 21, 2017 15:25
Show Gist options
  • Save novabyte/54217a8b8540e3b7c0d69104c6705a30 to your computer and use it in GitHub Desktop.
Save novabyte/54217a8b8540e3b7c0d69104c6705a30 to your computer and use it in GitHub Desktop.
Implement custom asynchronous match "rooms" on Nakama server.
--[[
Copyright 2017 The Nakama Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]--
local nk = require("nakama")
------------
-- A module which implements room-based matches which store state for a card game.
-- @module matchrooms
local M = {}
local matchrooms_mt = {
__name = "matchrooms_system",
__index = M
}
--- Display Matchrooms "object" in a readable way when printed.
function M:__tostring()
local matches_json = nk.json_encode(self.matches)
return ("matchrooms{bucket=%q, collection=%q, matches=%q}"):format(self.bucket, self.collection, matches_json)
end
--- Add a matchroom to the server unless it has been restored.
-- @param match_id The ID of the matchroom to add.
-- @param name The name for the matchroom.
function M:add_room(match_id, name)
local update_ops = {
{Op = "init", Path = "/state", Value = {}},
{Op = "init", Path = "/name", Value = name},
{Op = "replace", Path = "/name", Value = name}
}
local record = {
Bucket = self.bucket,
Collection = self.collection,
Record = match_id,
UserId = nil,
Update = update_ops,
PermissionRead = 2,
PermissionWrite = 0
}
nk.storage_update({ record })
self.matches[match_id] = nk.storage_fetch({ record })[1]
end
--- Check if a matchroom id has been defined.
-- @param match_id The ID of the match to validate.
function M:isvalid(match_id)
return self.matches[match_id] ~= nil
end
--- List matchrooms available on the server.
function M:list()
return self.matches
end
--- Join a match and receive the latest match state.
-- @param match_id The ID of the match to join.
-- @return The current state of the match.
function M:join_match(match_id, user_id)
local record = {
Bucket = self.bucket,
Collection = self.collection,
Record = match_id,
UserId = nil
}
local records = nk.storage_fetch({ record })
local match_state = records[1].Value.state
-- execute join callback
match_state = self.match_logic_onjoin(match_state, records[1].Value.name, user_id)
self.matches[match_id] = match_state
return match_state
end
--- Run logic callback on match state and update.
-- @param match_id The ID of the match.
-- @param user_id The ID of the user who's sent the command.
-- @param command The Lua table for the command message.
function M:send_command(match_id, user_id, command)
local record = {
Bucket = self.bucket,
Collection = self.collection,
Record = match_id,
UserId = nil
}
local records = nk.storage_fetch({ record })
local match_state = records[1].Value.state
match_state = self.match_logic_oncommand(match_state, match_id, user_id, command)
record.Value.state = match_state
record.PermissionRead = 2
record.PermissionWrite = 0
nk.storage_write({ record })
end
--- Restore matchrooms from the storage engine at server start.
-- @param bucket The bucket to store matches in.
-- @param collection The collection to store matches in.
local function restore_rooms(bucket, collection)
local matches = {}
local records = nk.storage_list(nil, bucket, collection, 100, nil)
for k, v in pairs(records)
do
matches[k] = v
end
return matches
end
--- Build a new matchrooms object.
-- @param bucket The bucket to store matches in.
-- @param collection The collection to store matches in.
local function new_matchrooms(bucket, collection)
bucket = bucket or error("'bucket' parameter must be set.")
collection = collection or error("'collection' parameter must be set.")
return setmetatable({
bucket = bucket,
collection = collection,
matches = restore_rooms(bucket, collection),
match_logic_oncommand = function(match_state, match_id, user_id, command)
nk.logger_info(("User %q played."):format(user_id))
return match_state
end,
match_logic_onjoin = function(match_state, match_id, user_id)
nk.logger_info(("User %q joined."):format(user_id))
return match_state
end
}, matchrooms_mt)
end
return {
new = new_matchrooms
}
--[[
Copyright 2017 The Nakama Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]--
local nk = require("nakama")
local matchrooms = require("matchrooms").new("mygamebucket", "matchrooms")
-- define logic for a match
matchroom:setup_matchlogic(
-- called on each command message from a user
function(match_state, match_id, user_id, command)
local command_json = nk.json_encode(command)
local match_state_json = nk.json_encode(match_state)
local message = ("match{match_id=%q, user_id=%q, command=%q, match_state=%q}"):format(match_id, user_id, command_json, match_state_json)
nk.logger_info(message)
--[[
ADD CUSTOM COMMAND LOGIC FOR GAME
]]--
-- must return match_state for sync
return match_state
end,
-- called when a user joins
function(match_state, match_id, user_id)
local match_state_json = nk.json_encode(match_state)
local message = ("match{match_id=%q, user_id=%q, match_state=%q}"):format(match_id, user_id, match_state_json)
nk.logger_info(message)
-- must return match_state for sync
return match_state
end)
-- add/restore rooms at startup
matchrooms:add_room("eca4a141-20a3-4a61-9957-f1986bc37bef", "Room 1")
matchrooms:add_room("27918c32-ad8e-4113-a82d-de7409c87ba1", "Room 2")
matchrooms:add_room("3d98b68a-e193-43b7-8a54-d19da2bcdd6d", "Room 3")
matchrooms:add_room("b0d20dfc-1c67-42e5-a11b-34cbb680b208", "Room 4")
matchrooms:add_room("41c1a0d4-9934-44bd-8efa-8e0392cf6cd9", "Room 5")
-- enable clients to "join" a matchroom
-- payload input:
-- { "id": "some_match_id" }
local function join_matchroom(context, payload)
local json = nk.json_decode(payload)
local match_id = json.id or error("'id' parameter must be sent in payload.")
local _ = matchrooms:isvalid(match_id) or error(("match with id %q does not exist."):format(match_id))
local match_state = matchrooms:join_match(match_id, context.UserId)
return nk.json_encode(match_state)
end
nk.register_rpc(join_matchroom, "join_matchroom_rpc")
-- enable clients to fetch available rooms
local function list_matchrooms(_context, _payload)
local allmatches = matchrooms:list()
return nk.json_encode(allmatches)
end
nk.register_rpc(list_matchrooms, "list_matchrooms_rpc")
-- send a command from a client to the match
-- payload input:
-- { "id": "some_match_id", "command": { "some": "json" } }
local function send_matchroom_command(context, payload)
local json = nk.json_decode(payload)
local match_id = json.id or error("'id' parameter must be sent in payload.")
local _ = matchrooms:isvalid(match_id) or error(("match with id %q does not exist."):format(match_id))
local command = json.command or error("'command' parameter must be sent in payload.")
matchrooms:send_command(match_id, context.UserId, command)
end
nk.register_rpc(send_matchroom_command, "send_matchroom_command_rpc")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment