I'm working on an educational project - online checkers (draughts) game and i'm still not feeling comfortable enough with socket.io.
What i'm trying to do is this - When a player wants to invite another player to a game room to play, using the socket.io I want to send both of the players from the lobby to the game room. Only they get sent to the game room, meaning that even if there are hundreds of players inside the lobby, only the 2 players that wants to play get redirected to the game room.
When player 1 clicks on a button, I emit his details and player's 2 details (The player that player 1 has chosen to play against) to the server as seen below:
socket.emit("SendPlayersToRoom", { player1, player2}, socket.id)
The socket.id argument is the socket.id of player 1. player 2 is an object that already includes his socket id at player2.id.
Next, I receive the information at the server side and then I try to emit this only to players 1 and 2, as seen below:
socket.on("SendPlayersToRoom", ({player1, player2}, id) => {
io.to(id).to(player2.id).emit("sendToRoom");
})
Finally, I receive the info at the client side where it should redirect both players to a new room where they can play:
socket.on("sendToRoom", () => {
location.href = `/game-room?p1=${player1.username}&p2=${player2.username}`
})
What actually happens with this code is that player 1 (The one who initiated the game invitation) gets sent to the room without player 2, player 2 stays at the lobby as if nothing happened.
What am I missing here? What am I doing wrong?
You have a unique id for your room. Make sure both sockets join it.
socket.join(id) - use this for both your sockets.
Related
I am making a 2-player rock-paper-scissors game using Discord.js.
Sadly the Discord API is really slow and afaik doesn't provide any type of middleware. When someone chooses their shape, the other person sees the reaction (or chat message) for quite a while until the bot deletes it, therefore ruining the whole game.
The only way of secretly getting an input I could think of, was sending the user a message in private chat, to which he can react. But having to switch from the server to private chat and then back to the server just makes the game unplayable in my opinion. It's just too much work for the user, compared to simply clicking a reaction.
Another option would be sending a message in the chat, which only a specific user can see. It could say something like "1 = Scissors, 2 = Rock, 3 = Paper". (The mapping would be randomized for each player). The user then picks the corresponding reaction from the options 1, 2 and 3.
But it seems, Discord does not allow to send a message in chat, which only a specific user can see. Or is there a way?
And is there any way of secetly getting user-input without the user having to switch chats?
Does the API maybe provide any kind of middle-ware for messages or reactions which I have overlooked?
Discord does not allow to send a message in chat, which only a specific user can see. Or is there a way?
No, there isn't. Discord API doesn't allow you to specify users that can see a specific guild message.
And is there any way of secetly getting user-input without the user having to switch chats?
There definitely is!
You could use a fairly new feature, buttons. Below is an example code, you can use to base your game on. Things left to implement are:
Gamestate, e.g. who is on turn, who has how much points, etc.
Update gamestates (identify gamestate by id?) in the interactionCreate event's callback.
Showing the gamestate to the players, e.g. updating the original message.
Don't allow players to modify the gamestate of other playing pairs.
Let the user specify an opponent in !createGame command.
The actual game logic (determining who won, who gets a point, etc.)
That is all I can think of for now. Take those more of a suggestions than requirements. There is no boundary to ones creativity.
// ... Some object to store the gamestates in? ...
client.on("messageCreate", async (message) => {
// Don't reply to bots
if (message.author.bot) return;
// Basic command handler, just for showcasing the buttons
if (message.content.startsWith("!createGame")) {
// ... Probably some argument handling for the opponent (e.g. a mention) ...
// Create an action row component with buttons attached to it
const actionRow = new Discord.MessageActionRow()
.addComponents(
[["Rock", "🗿"], ["Paper", "🧻"], ["Scissors", "✂️"]].map((buttonProperties) => {
return new Discord.MessageButton()
.setStyle("PRIMARY")
.setLabel(buttonProperties[0])
.setEmoji(buttonProperties[1])
.setCustomId(`rpsgame_${buttonProperties[0].toLowerCase()}`);
})
);
// Send the game message/playground
message.channel.send({
content: "Rock, Paper and Scissors: The game!",
components: [actionRow]
});
}
});
To handle the button clicks, we use the interactionCreate event.
client.on("interactionCreate", (interaction) => {
// If the interaction is a button
if (interaction.isButton()) {
// If the button belongs to our game
if (interaction.customId.startsWith("rpsgame")) {
// Log what the user has selected to console, as a test
console.log(`A user '${interaction.member.user.tag}' selected ${interaction.component.emoji.name}.`);
// Don't forget to reply to an interaction,
// otherwise an error will be shown on discord.
interaction.update("Update GameState...");
}
}
});
No one will see what the other users have selected, unless you want them to.
Using discord.js ^13.0.1. If you are on v12, there is a nice package called discord-buttons.
How can i pass DOM Elements server-side via socketio to display new message content.
// Client
// Pass DOM element as string
function elemToString(elem){
return elem.outerHTML
}
window.addEventListener("keyup", sendMessage, false);
function sendMessage(key) {
if (key.keyCode === 13) {
if (chatbox.value !== "") {
const child = document.createElement("div");
child.textContent = `${chatbox.value}`
child.className = "messages"
chat.appendChild(child);
socket.emit('chatMsg', elemToString(child))
chatbox.value = "";
chatbox.style.display = "none";
setTimeout(()=>{
child.remove()
},4000)
}
else chatbox.style.display = "none";
}
}
// User Class
class User {
constructor(name, x, y) {
this.alive = false
this.name = name
this.cells = []
this.room = 'none'
this.message = []
}
update(cells) {
this.cells = cells
}
}
module.exports = User
// Server
socket.on('chatMsg', elem => {
user.message.push(elem)
})
I have a hotkey which sends new divs which display message content. I want to pass the data server-side via socket.io so all users can see new message content instead of just the individual client.
The first question I would ask is whether you really need to pass around HTML or DOM objects? Based on the above snippet, it appears that sending the messages as strings (without any HTML markup, just the message body) is by far the easiest way for both the server and the chat client(s). As to sending the message to all users in the chat room, this feature is already built into socket.io (see https://socket.io/docs/rooms-and-namespaces/) and unless you need to implement custom logging or filtering, you could just use it out of the box
There are several ways you could go about building this chat server:
Simple / POC approach
If you just want to have a quick prototype and see if it does what you want it to do, and you are not too concerned with auditing, logging, filtering etc., then:
When the client connects, it "joins" a socket.io "room" (the name of the room could match the room on the client or you may choose to format/hash it etc.)
All chat room clients send events to that same room and listen to the events emitted by that room. The socket.io backend takes care of re-broadcasting the events, so you would not need to do anything
To send message to the room, the "sender" client would broadcast a well known event, something like { message: '<<message text>>' }. All clients listen to events in the room, and once a new event arrives they create a new HTML element (say, a <div>) with that message as content and append it to the chat window
This should cover the basic chat room scenario
A bit more involved chat server
In the real-world implementations, we typically want to have a bit more control over what events can and cannot be broadcasted (for example, we want to have the ability to silence a chatter or filter profanity/abusive language etc.) In this case, we cannot simply use blind broadcast, so typically the client would use two socket.io channels - a "room" for incoming messages and a direct channel for outgoing.
In this scenario, the server acts as a bridge - it listens to the incoming messages, does all the necessary filtering, preprocessing, sanitizes the messages to prevent injections etc., and then broadcasts the resulting message in the room. In this scenario:
All chat clients subscribe to the same "incoming message" room, just like in the previous scenario
When the chatter needs to send a message (say, they hit Enter), the client socket emits an event to the server and tells it which room this message is intended for (e.g. { incoming_message: 'xyz', user: 'username', room: 'room_21' }
The server receives the message and processes it (for example, checks the username against the list of banned users), then broadcasts the processed message in the room specified in the original message. The other chat participants receive the message and render it
In this scenario, you can keep the history of every message sent by a particular user (much like you do in your code snippet)
This link (https://socket.io/docs/#Sending-and-getting-data-acknowledgements) has a few examples on sending and broadcasting arbitrary JSON messages and also shows working with channels etc.
Hope that helps and good luck with your chat server!
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}`);
});
});
}
I'm new to the MEAN stack and trying to build a web app with it. With socket.io, I would like to randomly pair up all the users that are currently connected to the page.
My approach would be to use random integers in the range of the length of a list of all users connected, as indices of the randomly selected users in the array.
I know this is something that I can do to keep track of all clients/sockets connected:
io.sockets.on('connect', function(client) {
clients.push(client);
client.on('disconnect', function() {
clients.splice(clients.indexOf(client), 1);
});
});
But would it have issues with scaling? If so, what would be a better approach? Please advise.
I'd like to know if it's possible to prevent cheating in the following case
I have a Ruby on Rails app and a Active Record database
I have Users (model User), that play Games(model Game) and there are Prizes (model Prize).
What I want is to :
1- prevent a player from cheating/hacking on the winnings (prizes he has won)
2- prevent a player from cheating on the nb of shots he has
As a user can win multiple prizes and prizes can belong to multiple users, I have a many_to_many relations: i use for this a table/model Winnings that lists all stuff won in the games by each User (a user has many winnings and a prize has many winnings)
Players have a certain number of shots, let's say 3 per user.
For 1-, basically, i guess everytime a user wins a prize in a Game, i'll send the server a url like:
mygame/com/?winning_id=1234;game_id=3;user_id=6;prize_id=4, telling the server the user with id 6 has won a prize with id4 in the game with id 6
I don't want players to be able to cheat that. how can I do this. Can any player just use that url above and send this way a message/action to my server (post) telling him he won? that would make it freaking easy to cheat?
Should I encrypt stuff/the url and make the url/message only understandable by my server?
For 2- (shots), I think I should send actions to server side every time and calculate scores at server side but still can't he cheat the same way as 1-?
In addition to using post as mentioned in the answer from palainum above, if needed, you could add a string (instead of an ID) in any place in your game where the URL could be edited and is visible.
Background - I had a similar problem in one of my apps (where if a URL was changed people could deduce / edit the number and cheat). The way I overcame it was to generate a unique code for the item in the URL.
To do this, I would add a string attribute to Winning model called url_code, make it required and indexable:
add_column :winning, string :url_code, :null => false, :index => true
in your model add an after_initialize callback:
Class Winning
validates :url_code, :uniqueness => true
after_initialize :create_url_code
def create_url_code
self.url_code=SecureRandom.hex(4) if self.url_code.nil?
end
And use that as a parameter in the cases using an ID is a problem (similar to this in your controller)...
#winning = Winning.find_by_url_code(params[:id])
Also, you could do the same thing for your users URL (or if you need to display it every in URL) by using the user.name as a friendly_id.
edit - just fixed a typo where I had offer_code instead of url_code.
There are at least two things you can do now:
Send POST request - it will be still possible to cheat, but it will require more work
Create a model Win - and create object of this class after winning a game. Then after making request (point 1) you can check if this object exists.
EDIT:
Sorry, you already mentioned Winning class. Just create object of this class after winning a game, then check if a user won a game (if the table contains a record with the user and the game).
You should also store shots of a user in a game and use unique validation to disallow for example shooting twice in one game.