I am using JS and socket.io to make a multiplayer game.
The game player limit is ranging from 2-5.
What I want to achieve is :
1: On the client browser there is a dropdown list displaying all the game table names and its max player limit. A player can join any room by click on it.
2: Once the table player limit is reached, the room cannot be joined again.
3:players in each room can play their own game, chat in their own channel without being distracted.
In my current design there is a static room object and in the room project there is an array of pre-created tables object. The table object has many of the properties and methods that a game would need.
Server side room and table object:
function Room(name) {
this.players = [];
this.tables = [];
};
function Table(tableID){
this.id = tableID;
this.players = [];
this.maxPlayerLimit = 4;
};
I can pre-created amount number of tables and push these tables object to room's tables array.
Room.prototype.createTables = function(amount) {
var tableList = [];
for(var i = 0; i < amount; i++){
var table = new Table(i+1);
table.setName("Table" + (i + 1));
tableList.push(table);
}
return tableList;
};
client side when they click on the dropdown:
The drop down looks like https://jsfiddle.net/1oyy98uo/
socket.emit("connectToTable", {tableID:TableID,tableName: tableName,limit:limit}); //these info is based on their table selection in the dropdown
When a player connect to server I push them to the static room player array.
when they choose a table, I do
socket.on('connectToTable',function(data) {
var player = room.player(socket.id)// (room method to get the socket player)
var table = room.table(data.tableID); //(room method to get the table)
table.Name(data.tableName)//(table method to set name)
table.maxPlayerLimit = Number(data.limit)
if (tableisAvailable){
table.join(player)//table method to add player to its list
if(table.players.length===maxPlayerlimit){
init the game
}else{
client.emit("table is full")//then the client would know to go elsewhere
}
}
})
My design fulfills all the goals. but it is done in such a hard-coded way. The pre-created table might be too many or too little. The only way to solve this with my current design is to create more rooms than expected.
I am not sure if I should still stick with my current design or socket.io room feature can provide a better solution. (creating game tables on demand)
But there is a flaw in the on-demand scenario:
players make requests to join a room(table), once the requests reach to a certain limit (like 4) the server will join these players. This way the server will only create as many rooms(tables) as necessary. But these queued-up players are random players, they may not necessarily want to play with each other. If players want to play with their friends, this will be a problem.
With the Socket.io room feature, is there a more dynamic way or better way to create rooms(in my case is game tables) with different player limit ON DEMAND?
socket.io rooms are completely dynamic. You don't "create a room". Instead, you just join a socket to a room and that room will be automatically created. Similarly, you don't remove removes either. When the last socket leaves a room, the room just disappears.
So, if you use socket.io rooms instead of your Tables, then their creation will be entirely on demand. You can iterate the rooms in service at any time. You can iterate the sockets in a room at any time. You can add or remove a socket from a room at any time.
Related
I'm wondering if there is an easy way in lance-gg to send player specific data to each player only, rather than emitting all data to all players.
I wish to have create a poker game, and don't want the data around what each player is holding broadcast to all players and instead only have each player receive information regarding their own cards.
Is this easily achievable between the current GameEngine and ServerEngine?
Over the course of the game the following steps will need to occur:
"deal" cards to each player (emit cards to each player individually, and also broadcast an event to indicate the other clients should animate the player being dealt cards receiving them)
store and hold the dealt cards outside the other data to be updated
retrieve player cards should the player be disconnected and then reconnect during the hand
"reveal" cards (broadcast any revealed cards like the flop and the showdown cards to all players)
Player's cards also need to be stored on the server but not re-broadcast with each step.
There is a low-level networking layer which can be used for client-server communication in Lance.
For example, if the server wants to send the event shakeItUp with data shakeData = { ... } to all clients, the game's serverEngine would call:
this.io.sockets.emit('shakeItUp', shakeData);
To send events and data to specific players, the serverEngine class can do
for (let socketId of Object.keys(this.connectedPlayers)) {
let player = this.connectedPlayers[socketId];
let playerId = player.socket.playerId;
let message = `hello player ${playerId}`;
this.connectedPlayers[socketId].socket.emit('secret', message);
}
The client listens to messages from the ClientEngine sub-class, after the connection is established:
// extend ClientEngine connect to add own events
connect() {
return super.connect().then(() => {
this.socket.on('secret', (e) => {
console.log(`my secret: ${e}`);
});
});
}
Background
I am trying to design an interactive classroom type of environment. The room has different slides, a chat box and some other basic features.
My understanding
A sure way to update a page in real time for all users is for one person to persist a change via ajax to a database, then all the other users poll the server at a timed interval (one second) to check for changes... if there are their view gets updated.
My code
Each room has a unique URL... http://www.example.com/room/ajc73
Users slide through the clides using this code:
showCard();
function showCard() {
$('#card-' + (cardId)).show();
}
$('#nextCard').click(function() {
nextCard();
});
$('#previousCard').click(function() {
previousCard();
});
function nextCard() {
if ($('#card-' + (cardId + 1)).length != 0) { // if there is a next card
$('#card-' + (cardId)).hide(); // hide current card
cardId++; // increment the card id
$('#card-' + (cardId)).show(); // and show the next card
location.hash = cardId;
}
}
function previousCard() {
if (cardId != 1) { // if we are not at the first card
$('#card-' + (cardId)).hide(); // hide the current card
cardId--; // decrement the card id
$('#card-' + (cardId)).show(); // and show the previous card
location.hash = cardId;
}
}
My question
Am I required to persist data from user1 to the database in order for it to be called and displayed to user2 or is there a way to cut out the database part and push changes directly to the browser?
Go for websockets. that will be a better option. since its real-time and just a simpler logic will help you achieve the result.
If you are not sure that whether you will be able to use websockets(like if you are using shared hosting and your provider doesn't allow this or any other reason) you can go for various services like pusher(easier to understand) that will help to do your job but with some cost.
You could use sockets and just broadcast any input to every client.
Of course, the same can be done with ajax and a rest api but it'll be harder, i'll use pseudocode:
clients = {};
fn newclient() {
clients[client_id] = {
pool: [];
....
}
}
fn onNewMessage(newmessage) {
forEach(client, fn(c) {
c.pool.push(newmessage);
})
}
fn clientRequestNews() {
response = clients[client].pool;
clients[client].pool.length = 0;
return response;
}
the point here is, in server memory there would be a entry for each client, each of them has a pool, when a new message is sent to the server, it's pushed to every client's pool.
When a client ask's for news, the server will return the clients pool, after that, it'll clean the pool of that client.
With this you dont need persistence.
Use web sockets. Please see here
You need websockets, a datastructure server and a pub/serve model with events:
A hint
So, let's say I have an array of few socket ids (['RZ0_7yBdwyvlT8-bAAAA', 'iyeiRpVdmzAQSWyTAAAB', 'kSd2Iudt9SV29w9HAAAC']). On particular trigger, i want to move everyone from array to one socket.io room so i can emit events only to them. How can I do that??
You first need to get socket object from socketId and then call join("room_name") on it.
Let say room name is random.
var room_name = "random";
var ids = ['RZ0_7yBdwyvlT8-bAAAA', 'iyeiRpVdmzAQSWyTAAAB', 'kSd2Iudt9SV29w9HAAAC'];
ids.forEach(function(){
io.sockets.connected[id].join(room_name); // for v1.0
// io.sockets.sockets[id].join(room_name); // for V0.9
});
Let's say I have two users, "Matt" & "Kevin". Matt wants to message Kevin, by clicking a chat button to send Kevin a direct message a chat box boots up, he sends a message and Kevin receives it.
I generate the chat id by taking the person who sent it (Matt) and the person who received the message (Kevin) and concatenating it into an id.
var me = "Matt";
var user = "Kevin";
var uniqueChatID = me+user;
As I save the message server side (with mongoDB) the message object has a chatID of MattKevin. So now when I want to get back to that chat I can pull in all messages with the chatID of MattKevin.
This works fine, until Kevin wants to boot up a chat with Matt, then the id becomes KevinMatt. Now I am referencing a different chat, it's backwards. So If I want to pass uniqueChatID to get the messages it will pull a different set.
var me = "Kevin";
var user = "Matt";
var uniqueChatID = me+user;
So I am curious how can I set this up a bit better so that my program knows, ok Matt and Kevin have a chat, so if Matt messages Kevin it pulls in their chat or visa versa, Kevin messages Matt and it gets the same messages?
Sort them alphabetically:
var me = "Kevin";
var user = "Matt";
var uniqueChatID = [me, user].sort().join('');
That said, while this technically works, I'd recommend you do a little housekeeping - ensure they're always lowercase, and ensure on your db that you enforce unique usernames. Or, I'd even suggest giving the user a unique identifier (like a UUID) and use that instead to create the UCID:
var me = CurrentUser.uuid(); // 8cb3ebb8-30f9-11e5-a151-feff819cdc9f
var targetUser = Chat.targetUser(); // Matt: 9bc1ef9c-6719-4041-afd3-c5b87c90690d
var uniqueChatID = [me, targetUser].sort().join(',');
// 8cb3ebb8-30f9-11e5-a151-feff819cdc9f,9bc1ef9c-6719-4041-afd3-c5b87c90690d
And lastly, if your db supports relationships or connections, your best option is to separate chat table/collection for each chat and "connect" (or create a relationship) between both users and the chat. Then the next time you go and load it up, the connection will lead you to a unique chat that's connected to both users.
I think you approach is too complex. Furthermore, it looks like you want to embed the individual chat messages into the document bearing the created _id. The problem here is that there is a 16 MB size limit on BSON documents at the time of this writing. Upon reaching this limit, your users simply could not communicate any more. Increasing the size of documents may also lead to frequent document relocations, which is a very costly operation unless you use the new WiredTiger storage engine introduced in version 3.0 of MongoDB.
So we need a more scalable approach.
Here is how I would do it:
User:
{
_id: "Kevin",
email: "kevin#example.com"
/* Put further user details as you see fit*/
}
Message:
{
_id: new ObjectId(),
from: "Kevin",
/* You might want to have multi-person chats, hence the array */
to: ["Matt"],
ts: new ISODate(),
message: "Hi, Matt!"
}
Index:
db.messages.ensureIndex({from:1,to:1,ts:1})
Query for reconstructing all messages a user received:
var user = "Matt"
db.messages.find({"to": user}).sort({ts:1})
Now you can iterate over the result set and open a chat window for each "from" you find.
Query for reconstructing a defined chat
var user = "Matt"
var sender = "Kevin"
db.messages.find({"from": sender, "to":user}).sort({ts:1})
will give you all messages sent to Matt by Kevin, ordered by time. Since both queries should utilize the index, they should be pretty fast. You can use .limit(x) to query only the last x messages sent to user.
With this approach, you don't need an artificial _id, the index created allows you to do every query related to the participants efficiently and the messages can be sorted in order. Because each message is saved individually and does not change any more, you can store an almost indefinite number of messages and bypass the document relocation problem.
I have an array in if Meteor.isServer that pushes userIds to the array when they login and removes said userid on logout. In order to do this I am using the Profile-Online package (https://github.com/erundook/meteor-profile-online). I want to make unique templates depending on the amount of users being 0, 1 or 2 users connected I need to make the array in the server side available to the client without it I can't make templates based on the amount of users. I tried to publish the array in the server and subscribe in client. I have an idea I'm doing something totally and completely wrong. My main objective is to make the array(playerArray) accessible from the client.
JS file:
Games = new Meteor.Collection("games")
if (Meteor.isClient) {
//testing if subscribe returns IDs from the array which it doesn't
Meteor.subscribe("ingame",function(test){console.log(test)})
}
if (Meteor.isServer) {
playerArray = []
Meteor._onLogin = function (userId){
if(!_.contains(playerArray, userId)){
playerArray.push(userId)
}
console.log(playerArray)
}
Meteor._onLogout = function (userId){
playerArray = _.without(playerArray, userId);
console.log(playerArray);
}
Meteor.publish("ingame",function(){
return playerArray
})
}
I'm quite the newbie at meteor, but this should work:
create a collection CurrentUsers into which you insert on _onLogin and from which you remove on _onLogout,
then on the client's side ask ClientUsers.find({}).count() and it return the number of logged in users
I suspect that the array approach isn't scalable (won't work for multiple node processes), but this should work OK