socket.io multiplayer game player's view - node.js

I'm very new to nodejs and socket.io. I'm trying to create a simple multiplayer card game in real time. my problem right now is I can't get the player's view to display on the correct div from player's perspective. example is.. I want my playing area to display at the bottom box and my opponent to display on top box and same goes with my opponent's viewer. he will see himself on the bottom div and sees me on the top div. How do u get that effect to work?
client
<div class='top'></div>
<div class='bottom></div>
<script>
var socket = io();
socket.on('playerinfo', function(data){
$('.top').html(data[0]);
$('.bottom')html(data[1]);
});
</script>
server
var players = [];
io.on('connection', function(socket){
//player is given 'P' with random number when connection is made to represent username
socket.name = "P" + Math.floor(Math.random() * (20000));
players.push(socket.name);
io.emit('playerinfo', players);
}
Also it would be great if you can point me to a tutorial or blog related to this. thank you.

You are differentiating the self and other user by some uniqueness right, say for example uniqueness is user email or user id.
Solution 1:
On making socket connection, send the user id/email also and you can store that as part of socket object itself. So that when ever player1 did some move, on emit send the id also along with whatever data you are sending.
Solution 2:
When player1 did some move, you will send data to server, while sending the data, send the user id/email also. And in server again emit along with user id.
In client you can check - if id is self, then update at bottom. If id is not self then update the top. Note: If you have multiple opponent player, still you can handle with this.
EXAMPLE:
In client:
<script>
var socket = io();
var selfId;
socket.on('playerinfo', function(data){
selfId = data.name;
playerInfo = data.players;
$('.top').html(playerInfo);
$('.bottom')html(selfId);
});
socket.on('move', function(data){
if(data.uid == selfId)
{
$('.top').html(data.card);
}
else
{
$('.bottom')html(data.card);
}
});
socket.emit('move', {card:A, uid:56836480193347838764});
</script>
In server:
var players = [];
io.on('connection', function(socket){
//player is given 'P' with random number when connection is made to represent username
socket.name = "P" + Math.floor(Math.random() * (20000));
// Here may be you can assign the position also where the user is sitting along with the user name.
//players will be an array of object which holds username and their position
//So that in client you can decide where the user will sit.
players.push(socket.name);
io.emit('playerinfo', {'players':players, 'name':socket.name);
socket.on('move', function (data) {
socket.emit('move', data);
});
}

Take a look into this 7 part tutorial. I think it gives you a good picture about what needs to be done in a typical multiplayer game by Socketio:
http://www.tamas.io/online-card-game-with-node-js-and-socket-io-episode-1/
http://www.tamas.io/online-card-game-with-node-js-and-socket-io-episode-2/
http://www.tamas.io/online-card-game-with-node-js-and-socket-io-episode-3/
http://www.tamas.io/online-card-game-with-node-js-and-socket-io-episode-4/
http://www.tamas.io/online-card-game-with-node-js-and-socket-io-episode-5/
http://www.tamas.io/online-card-game-with-node-js-and-socket-io-episode-6/
http://www.tamas.io/online-card-game-with-node-js-and-socket-io-episode-7

You need to give each client some kind of id, and tell each client when they connect, what their id is. When a client connects, generate a random unique id for them, and then send this back to them so they know their id. When you send back the data of other players, the client can figure out which data is theirs based on the id and display it in the right area.

Related

Socket.io not sending to one socket in a room

I'm using socket.io to handle some of the server-client communication and matchmaking for a simple multiplayer game.
(for now) im automatically joining 2 players together by adding them into a socket.io room. When a room has 2 players in it I emit a "startGame" event to the room using socket.to(ROOM).emit(EVENT, EVENT_MSG) after doing a check
server side nodeJS:
game_start_state = checkRooms(socket, freeRooms);
if (game_start_state){
console.log("told room", game_start_state, "to start their game!")
socket.to(game_start_state).emit("startGame", game_start_state);
}
but so far only the first socket that gets connected to the room receives the "startGame" event message, I've looked around and havent seen anyone else with the same problem. Below is the code that is fired after the client emits an event saying it wants to join a room.
server side nodeJS:
function checkRooms(socket, roomArray) {
// auto-matchmaking logic
if(!roomArray || !roomArray.length){
//if there is no room with space create a new one
const room = uuid();
r_list.push(room); // r_list is just an array used to keep track of opened rooms for displaying to the user through some html
freeRooms.push(room); // freeRooms is an array with rooms with just 1 socket connected
joinRoom(socket, room);
return(null);
} else {
// if there is a room with a space, try to connect the client to it
const room = freeRooms[0];
console.log(socket.id, "wants to join", room);
// connect client to rooms
joinRoom(socket, room);
// room is now full so start the game
freeRooms.pop(room);
return(room);
}
}
because for now there is only auto matchmaking, there will only be 1 room in the freeRooms array so I'm not worries about this.
Does anyone know where I could be messing up? Can provide more code examples if necessary.
socket.to(room).emit(...)
sends to every member of the room EXCEPT the referenced socket. You can see that documented here.
If you want to send to everyone in the room, then use:
io.to(room).emit(...)

How to implement sending messages to some user not to all sockets

How to send message instead to all clients in room, to specific user in default room based on username or id. Where I need to implement that, only on server site or? I know I am doing wrong and I am confused.
var numUsers = 0;
io.on('connection', (socket) => {
var addedUser = false;
// when the client emits 'new message', this listens and executes
socket.on('new message', (data) => {
// we tell the client to execute 'new message'
socket.broadcast.emit('new message', {
username: socket.username,
message: data
});
});
I see in docs that code https://socket.io/docs/rooms-and-namespaces/ and how can I handle the ID for users, they changed in every re-connection
Here is what you can do:
1.You can use your own data structure to store users information like socketId and other things or go for redis it is a good in memory data structure.
2.You have to store socketId for each user when a new connection is made on server using any of the above or some other method.
3.when you want to send the message to a particular user you need have it's socketId and use this code to send message:
io.to(`${socketId}`).emit('hey', 'How are you');
4.When a user disconnects just delete the user's info like socketId and other info which you have.
So, when a user join your server save his info and when he leaves just delete it.
Now suppose you have four users connected on your server. just create a array of object to keep the track of users and socket ids. when new user connects just push into the array and when a user exit just loop through the array and when socketId matches just remove that object from array simple
let users = [{name:'a',id:'socketId'},{name:'b',id:'socketId'},{name:'c',id:'socketId'},{name:'d',id:'socketId'}]
if you want to send message to user b only just loop through the array and match the name property and when you find the matching name just fetch the id of that user and send it away
for(let i=0 ; i<users.length ; i++){
if(users[i].name == 'b'){
io.to(users[i].id).emit('hey', 'How are you');
}
}
Every connection has a socket id but it will be different every time a user opens a new tab on the browser. So if a user opens two tabs, they will have two different socketIds.
So the better approach would be to use rooms. Every time a user connects you can add it to a room. The room name can be userId or email.
socket.join(userid)
Then whenever you want to send a message to a particular user just use
io.sockets.in(userid).emit('message',"hello");
This will work even if the user has multiple tabs open on their device.

Move another user to a room with Socket.io

I am using Socket.io with Node.js and I would like to pair users up and send them to a new room.
When a user connects, within io.on('connection') I determine their compatibility with a user waiting to be paired in an array.
If the user who has joined is compatible with a waiting user, I want to move them both to a new room.
This is my current approach. Note: [1029387,1983934,9243802] is an array of user IDs.
var pendingPlayers = {
"spelling": {
"level1":[1029387,1983934,9243802],
"level2":[]
}
}
io.on('connection', function(socket) {
// check compatibility
// move current player and other queued player to new room
});
The only idea I have is to socket.emit('room', 'new room name'); from server to client, for the queued player to send this ID back to the server and then use:
io.on('connection', function(socket) {
socket.on('room', function(room) {
socket.join(room);
});
});
However, sending the room name to the client, to then send it back to the server seems awfully far-fetched. I'm hoping there's an easier way.
Your suggestions are gratefully received.
As per your comment
If there are no players already in the pendingPlayers object for the
respective level, the user who is joining a game is added to the
pendingPlayers object.
Rather than moving to object, make a new room with only this player for that level. Now when new player come as per your comment
Then, the next player who requests to join the respective level will
be paired with the player ID that is .push()'ed from the array
corresponding that level.
make that next player join the above room.
Maybe you need some logic like in the battleship game. It is used temporary 'waiting room'. I found it here:
https://github.com/inf123/NodeBattleship/blob/master/server.js
io.on('connection', function (socket) {
//firstly add player to room until opponent aren't come
socket.join('waiting room');
joinWaitingPlayers();
});
function joinWaitingPlayers () {
var clients = [];
for (var id in io.sockets.adapter.rooms['waiting room']) {
clients.push(io.sockets.adapter.nsp.connected[id]);
}
if (clients.length >= 2) {
//if we have a couple, then start the game
var game = new Game();
// live "waiting room"
clients[0].leave('waiting room');
clients[1].leave('waiting room');
// and then join both to another room
clients[0].join('game' + game.id);
clients[1].join('game' + game.id);
}
}

Nodejs - Socket.io how to detect which room sent the data?

I'm building a game where in order to players start a game, they should press a button, when everyone in the same room clicked the buttton the game would start.
The problem is that I managed to do this only without specifying which room sent the "I'm ready message" so I'm only counting the number of connected players regardless of where they came from:
Code to give an example:
socket.on('userReady', function(){
userReadyCounter++;
if(userReadyCounter === connectionCounter){
gameDuration = 180
io.sockets.in(socket.room).emit('startGame');
io.sockets.in(socket.room).emit('newSentence', sentences[socket.sentenceCounter]);
}
})
Connection counter part:
socket.on('joinedRoom', function(roomData){
socket.username = roomData.username;
socket.room = roomData.roomname;
socket.sentenceCounter = 0;
connectionCounter++;
socket.join(socket.room);
socket.broadcast.to(socket.room).emit('userJoined', socket.username);
});
Client:
function userReady(){
socket.emit('userReady');
}
So everytime a user send the message I'm unable to tell where they came from...
Am I doing this incorrectly?
You cannot detect on the client what room sent the data. For starters, rooms don't send messages or data. A server sends the data. The server may iterate through all connections in a room and send to them, but the message is not actually sent by the room - it's sent by the server. And, the message simply doesn't include any info in it about what room it was associated with.
So, the only way you're going to know which room a message is associated with in the client is if you either created some previous association with a room so the client just knows that messages it receives are associated with a particular room or if you send the actual room that the message is associated with inside the message itself. That would be the simplest scheme:
socket.room = roomData.roomname;
socket.join(socket.room);
socket.broadcast.to(socket.room).emit('userJoined', {
user: socket.username,
room: socket.room
});
Then, every message like this that arrives on the client informs the client exactly which room it is associated with.

Random chat with two users at a time (Socket.io)

I just started learning NodeJS and Socket.io. Until now I have this demo code, from official socket.io site:
http://socket.io/demos/chat/
I am able to get the unique client's ID of each user (socket) which connects, I am still trying to figure out, How can I make my code to only connect with 1 random user at a time when somebody runs the application. I just want to make random chat like Omegle (http://www.omegle.com/).
Only two users should randomly connect and chat with each other till they re-run the app, if they come back they should get connected with someone else who is in the online queue.
What changes do I need to do to have a similar behaviour?
Update
Added Client site code, main.js
$(function() {
var FADE_TIME = 150; // ms
var TYPING_TIMER_LENGTH = 400; // ms
var COLORS = [
'#e21400', '#91580f', '#f8a700', '#f78b00',
'#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
'#3b88eb', '#3824aa', '#a700ff', '#d300e7'
];
// Initialize variables
var $window = $(window);
var $usernameInput = $('.usernameInput'); // Input for username
var $messages = $('.messages'); // Messages area
var $inputMessage = $('.inputMessage'); // Input message input box
var $loginPage = $('.login.page'); // The login page
var $chatPage = $('.chat.page'); // The chatroom page
// Prompt for setting a username
var username;
var connected = false;
var typing = false;
var lastTypingTime;
var $currentInput = $usernameInput.focus();
//Own Global
var room = '';
var socket = io();
function addParticipantsMessage (data) {
var message = '';
if (data.numUsers === 1) {
// message += "there's 1 participant";
// Status Message
message += "Waiting to connect with someone";
} else {
// message += "there are " + data.numUsers + " participants";
//Status message update
message = "You are connected to a stranger! Say Hey!";
}
log(message);
}
// Sets the client's username
function setUsername () {
username = cleanInput($usernameInput.val().trim());
// If the username is valid
if (username) {
$loginPage.fadeOut();
$chatPage.show();
$loginPage.off('click');
$currentInput = $inputMessage.focus();
// Tell the server your username
socket.emit('add user', username);
// Own
socket.emit('login', {'username' : 'Faizan'});
}
}
Although I would close this question because it's too vague, I feel obliged to give you some insight since I worked way too much with websockets in the last years (although not that much with socketio & nodejs). I suppose some simple guide and relevant links could help you. So first,
Kind of relevant intro
You should already know that Socket.io is a WebSocket implementation.
WebSockets (WS) allow server to send data whenever it wants, as long as the connection is still open, as opposed to old way: client querying all the time asking, if there is an update on the server.
You can imagine a woman and a man at the end of a party: "Thanks for tonight, I'd love to repeat it sometimes soon. Would you give me your number?" - asks the old man. "Ughhh, you know what, better give me yours, I promise I will call you!"
If the girl were to give him her number, he'd call a few times a day asking if she'd go somewhere (and she'd reply no). The other way around, she would call him only if she wanted to go and he would go. Of course he would.
I got a bit carried away, but you get the picture. The woman is a server, the guy is a client.
What is important to understand
(Absolute basic, you should know this =>)
When client connect to your server, (s)he should be served a html page and some javascript, which establishes connection to your WS server. In the code you've posted, Express is used as http server. Check this example to see how you should give user html&js.
You'll also notice namespaces and rooms in most of these tutorials. These are used for separating users into subcategories. One server may contain multiple namespaces (by default only one) and each namespace may contain multiple rooms. You probably won't need to bother with namespaces, one is just enough for your case. You will, however, need to understand rooms (more on that later).
Next thing, taken from your code
io.on('connection', function (socket) {
It's important to know, that socket here basically represent one connected client (in one namespace, but possibly in multiple rooms). You can do all sort of stuff with it, most notably:
install event handlers on it (that's what you do when you call socket.on(event, handler(data))
send events to it with socket.emit(event, data)
send broadcast event to all users with socket.broadcast.emit(event, data)
add/remove it to/from room with socket.join(room), socket.leave(room) respectively.
work with it as with an ordinary variable - store it wherever you want and then reuse it
Do you see the definition of numUsers in your code? That's a global variable which is shared with all clients, since nodejs is single-threaded. In the example it is being incremented inside one of the event handlers. Think we could use something like that? YES.
We can define global variable, queue for example. Or Q if you want. Point is, it can be an array used to store sockets, or rather clients, which are not currently in chat room.
At the end of this section I'd like to point out another obvious thing.
io.on('connection', handler); defines an event handler for 'connection' event happening on the io object (WS server). This is triggered each time client makes connection to your WS server (in your case, through javascript ran inside client's browser). Argument to the method is socket and it is this method where you should add event listeners for each client (that you already do in the code, particularly handling events 'new message', 'add user', 'typing', 'stop typing' and 'disconnect').
What events shall you need
That really depends on how complex you want your app to be. In my opinion, the bare minimum would be (note that you can change the event names, but 'disconnect' should stay 'disconnect'):
event name -> data given
Events handled on server side
login -> username (how the user should be called), possibly password if you want to enable registration
message -> text (content of the message being sent)
leave room -> room name
disconnect
Event handled on client side
connect
chat start -> name (second client's name), room (so we can leave it)
chat end -> no data required if you want to allow only one chat at the same time. In case of multiple chats you should also include which chat got closed
disconnect
Last note before we get started
This is only a rough sketch. There are multiple different crossroads along the way and which path you take mostly depends on your idea of the app. If you want to have multiple chats opened at the same time, you'll need to do some modifications. The same goes if you want to have more than two people connected to the same chat. Here I'll describe the simplest case possible, one chat, to people, no registration. Possibly what you want, judging from your post. Could be wrong.
Workflow
User opens your page in their web browser. You serve them html and javascript. The javascript will start new connection to your websocket server. Also, handlers for desired events should be defined at this point.
When the connection is established, this will be happening:
ON SERVER SIDE
io.on('connection', handler) will be fired. Only appropriate handlers for new socket will be installed, not doing anything else at this point.
ON CLIENT SIDE
socket.on('connect', handler) will be fired. Client should at that point have username stored somewhere. If not, no problem. The connection will be alive for quite some time. You can just call socket.emit('login', {'username':name) any time you wish after you are connected (in the example below I set up variable connected, which defaults to false but will be set to true as soon as connection is established.)
After you send login event from client, server registers it and saves it somewhere. Possibilities are endless, in this case I'll create global dictionary which maps socket.id to username. After that, user socket should be either paired with another one or added to queue.
So, if the queue is empty, simply append socket to global variable (it doesn't have to be an array, since we will pair the first available sockets together, however you may want to implement some history of users so they won't get connected to the same person again). If the queue is not empty, we pull one socket out of the Q and add them to the same room. Room name can be random or whatever you want, I'll use (socket1.id+'#'+socket2.id (if you wanted to have more users in one chat, this would have to be changed).
After you add them both, you'll need to notify them that their chat has started and send them the other peer's name. You will emit event 'chat start'.
Clients will catch the event and open new window. After that, whenever user types something and sends it, client emits event 'message' with payload {'message': user_inserted_text}. Server will capture it in the .on('message' handler and broadcast it to the room. Note:
Broadcasting means sending a message to everyone else except for the socket that starts it.
Note: I am really confused about socketio code right now. Look at this and tell me, if socket.rooms is an array or an object (socket.rooms[room] = room; ?? why?)
To avoid dealing with this not-straightforward code, lets create another global object, rooms, which will store the room names for us. We will map socket.id -> roomName there.
So when message comes, we can get name of the room by calling rooms[socket.id]. Then we broadcast the message like this:
socket.broadcast.to(room).emit('message', data);
Where data is what we received from the sender, therefore object {'text': 'some nice message'}. Your peer will then receive it (you won't) and display it (you should display it when you are sending it).
So the chat continues like this for a while, then one of the users decides (s)he wants to leave / chat with somebody else. They will close window and client will emit event 'leave room'. Server will capture it and send to the other party that her/his peer has disconnected. The same should happen if the client disconnects. After everything is closed, add both users to queue (or only one, if the other has disconnected from the server). In my code I will not make sure they won't get paired again. That is for the OP to code (can't be hard).
So, if you read this far, you deserve some actual code. Although I say actual, it's actually untested. But you know, it should work like this.
Some code
Client side
var connected = false;
var username = 'Faizan';
var room = '';
var socket = io('http://localhost');
socket.on('connect', function (data) { // we are connected, should send our name
connected = true;
if (username) socket.emit('login', {'username' : username});
});
socket.on('chat start', function(data) {
room = data.room;
show_chat_window(data.name); // some method which will show chat window
});
socket.on('chat end', function(data) {
hide_chat_window(); // this will close chat window and alert user that the peer ended chat
socket.leave(room); // it's possible to leave from both server and client, hoever it is better to be done by the client in this case
room = '';
});
socket.on('disconnect', function(data) { // handle server/connection falling
console.log('Connection fell or your browser is closing.');
});
var send_message = function(text) { // method, which you will call when user hits enter in input field
if (connected) socket.emit('message', {'text': text});
};
var leave_chat = function() { // call this when user want to end current chat
if (connected) {
socket.emit('leave room');
socket.leave(room);
room = '';
}
};
Server side
Not including initial requires and html/js serving., only global definitions and main io handler.
var queue = []; // list of sockets waiting for peers
var rooms = {}; // map socket.id => room
var names = {}; // map socket.id => name
var allUsers = {}; // map socket.id => socket
var findPeerForLoneSocket = function(socket) {
// this is place for possibly some extensive logic
// which can involve preventing two people pairing multiple times
if (queue) {
// somebody is in queue, pair them!
var peer = queue.pop();
var room = socket.id + '#' + peer.id;
// join them both
peer.join(room);
socket.join(room);
// register rooms to their names
rooms[peer.id] = room;
rooms[socket.id] = room;
// exchange names between the two of them and start the chat
peer.emit('chat start', {'name': names[socket.id], 'room':room});
socket.emit('chat start', {'name': names[peer.id], 'room':room});
} else {
// queue is empty, add our lone socket
queue.push(socket);
}
}
io.on('connection', function (socket) {
console.log('User '+socket.id + ' connected');
socket.on('login', function (data) {
names[socket.id] = data.username;
allUsers[socket.id] = socket;
// now check if sb is in queue
findPeerForLoneSocket(socket);
});
socket.on('message', function (data) {
var room = rooms[socket.id];
socket.broadcast.to(room).emit('message', data);
});
socket.on('leave room', function () {
var room = rooms[socket.id];
socket.broadcast.to(room).emit('chat end');
var peerID = room.split('#');
peerID = peerID[0] === socket.id ? peerID[1] : peerID[0];
// add both current and peer to the queue
findPeerForLoneSocket(allUsers[peerID]);
findPeerForLoneSocket(socket);
});
socket.on('disconnect', function () {
var room = rooms[socket.id];
socket.broadcast.to(room).emit('chat end');
var peerID = room.split('#');
peerID = peerID[0] === socket.id ? peerID[1] : peerID[0];
// current socket left, add the other one to the queue
findPeerForLoneSocket(allUsers[peerID]);
});
});
P.S.
The code above got a bit messy in the end. It can be done better and I encourage you to do better job than I did. Having this material at hand, go through it step by step and try to understand. I think I commented most, if not all of it. Good luck.
Tl;dr
I am not even surprised. Here, read a comic

Resources