Streaming data from the Server to Client with Meteor: - javascript

I'm developing a text based adventure game with Meteor and I'm running into an issue with how to handle certain elements. Namely, how to emit data from the Server to the Client without any input from the Client.
The idea is that when a player is engaged in combat with a monster, the combat damage and updating the Player and Monster objects will be occurring in a loop on the server. When the player takes damage it should accordingly update the client UI. Is something like this possible with Publish / Subscribe?
I basically want something that sits and listens for events from the server to update the combat log accordingly.
In pseudo-code, this is something along the lines of what I'm looking for:
// Client Side:
Meteor.call("runCommand", "attack monster");
// Server Side
Meteor.methods({
runCommand: function(input) {
// Take input, run the loop to begin combat,
// whenever the user takes damage update the
// client UI and output a line saying how much
// damage the player just received and by who
}
});
I understand that you can publish a collection to the client, but that's not really as specific of a function I'm looking for, I don't want to publish the entire Player object to the client, I just want to tell the client to write a line to a textbox saying something like "You were hit for 12 damage by a monster!".
I was hoping there was a function similar to SocketIO where I could, if I wanted to, just emit an event to the client telling it to update the UI. I think I can use SocketIO for this if I need to, but people seemed to be adamant that something like this was doable with Meteor entirely without SocketIO, I just don't really understand how.
The only outs I see to this scenario are: writing all of the game logic client-side which feels like a bad idea, writing all of the combat logs to a collection which seems extremely excessive (but maybe it's not?), or using some sort of SocketIO type-tool to just emit messages to the client to tell it to write a new line to the text box.

Using Meteor, create a combat log collection seem to be the simplest option you have.
You can only listen on added event and then clear the collection when the combat is over.
It should be something like this :
var cursor = Combat_Log.find();
var handleCombatLog = cursor.observe({
added: function (tmp)
{
// do your stuff
}
});
I ask a similar question here, hope this will help ^^

Here's how I did it without a collection. I think you are right to be concerned about creating one. That would not be a good idea. First install Streamy.
https://atmospherejs.com/yuukan/streamy
Then on the server
//find active sockets for the user by id
var sockets = Streamy.socketsForUsers(USER_ID_HERE)._sockets
if (!Array.isArray(sockets) || !sockets.length) {
//user is not logged in
} else {
//send event to all open browser windows for the user
sockets.forEach((socket) => {
Streamy.emit('ddpEvent', { yourKey:yourValue }, socket);
})
}
Then in the client, respond to it like this:
Streamy.on('ddpEvent', function(data) {
console.log("data is ", data); //prints out {yourKey:yourValue}
});

Related

Discord.js - Secretly getting user input

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.

Inform user that message is still being typed

I am using Laravel 5.6.7, Socket.IO and vue.js. I am not using Pusher and redis. Below is my code to send message to user chatting with me one to one.
var url = "http://localhost:6001/apps/My_appId/events?auth_key=My_Key";
var socketId = Echo.socketId();
var request = {
"channel": "private-Send-Message-Channel.2",
"name": "MessengerEvent",
"data": {
"msg": message
},
"socket_id": socketId
};
axios.post(url, JSON.stringify(request)).then((response) => {
//Message Sent
});
I am trying to inform user who is chatting with me that I am still typing. Should I use the same above code which emits xhr on each char type? Is it the only way to inform user that the message typing is still in progress?
Update 1
Is there any better way to post xhr as mentioned above for each key press? I meant if user types 200 chars. will I post xhr 200 times?
or
Do we have an event called whisper and listenForWhisper as shown here https://laravel.com/docs/5.6/broadcasting#client-events ? I am using vue.js and laravel 5.6.7 without pusher and without redis
If you look at the broadcasting documentation you will see two code code snippets which you can use in your Vue.js application.
To broadcast client events, you may use Echo's whisper method:
Echo.private('chat')
.whisper('typing', {
name: this.user.name
});
To listen for client events, you may use the listenForWhisper method:
Echo.private('chat')
.listenForWhisper('typing', (e) => {
console.log(e.name);
});
While the user is typing, you can debounce the whisper method above.
If you don't wish to use another library like lodash, you can implement the debounce by simply wrapping whisper in a timeout. The following method would broadcast the whisper every 300ms:
isTyping() {
let channel = Echo.private('chat');
setTimeout(function() {
channel.whisper('typing', {
name: this.user.name,
typing: true
});
}, 300);
}
The app needs to trigger isTyping() when an onkeydown event occurs in the chat application's input field.
You also need to listen for the whisper once the app is created. The following method will set the typing variable to true for 600ms after the event has been received.
created() {
let _this = this;
Echo.private('chat')
.listenForWhisper('typing', (e) => {
this.user = e.name;
this.typing = e.typing;
// remove is typing indicator after 0.6s
setTimeout(function() {
_this.typing = false
}, 600);
});
},
I am no Laravel expert, but I've faced this problem before.
First, let's define what "typing" means. The simplest way to define it is to say that a user is typing if and only if the input field to send a message is not empty.
This is not perfect, because the user can go away from keyboard in the middle of typing a message then not returning to complete and/or send it, but it is good enough.
More importantly, we now don't need to care about key strokes to know if the user is typing. In fact, "user is typing" now becomes as easy as chat_input_box.length > 0 to represent in code.
This boolean value is what needs to be synced across users/servers, not the act of hitting a key on the keyboard by the user. However, to keep the value up to date, we need to catch input events on chat_input_box and if the boolean value has changed since before this current event has occurred, socket.io should be able send a signal signifying whether the user has stopped or started typing.
On the receiving side, this signal toggles appropriate views to appear or disappear to indicate the state of the app to the user in human terms.
For the shortcoming of a user typing something then leaving, a timeout can be set so that when it is finished the boolean value "is typing" resets to false, while the act of typing something resets the timeout then starts it again automatically.
You don't have to send an xhr request to your app. You can just
broadcast events directly to the chat users without hitting your app.
From the Laravel docs:
Sometimes you may wish to broadcast an event to other connected clients without hitting your Laravel application at all. This can be particularly useful for things like "typing" notifications, where you want to alert users of your application that another user is typing a message on a given screen. To broadcast client events, you may use Echo's whisper method:
Echo.private('chat')
.whisper('typing', {
name: this.user.name
});
To listen for client events, you may use the listenForWhisper method:
Echo.private('chat')
.listenForWhisper('typing', (e) => {
console.log(e.name);
});
Yes you are right, it should not be emitting on every character change instead you could use debouncing to wait for a small time and then fire the function.
I would recommend using lodash library's debounce method. It should be something like this.
Kindly have a look at the documentation: https://lodash.com/docs/4.17.5#debounce
Laravel's Echo also sounds good, as you'll be doing nothing with typing action on the back-end thus just emitting from client to client is better than involving the server.

How to throw a custom message using Dialogflow after three times of fallback

I am developing a chatbot using Dialogflow, I would like to throw a message to user when the chatbot doesn't understand the user input for three times in a row and for the forth time respond with a custom message (not the one of the options declared on the dialogflow interface)
One idea that I have is to make a counter within the input unknown action like this:
var counter = 1;
// The default fallback intent has been matched, try to recover (https://dialogflow.com/docs/intents#fallback_intents)
'input.unknown': () => {
// Use the Actions on Google lib to respond to Google requests; for other requests use JSON
if (requestSource === googleAssistantRequest) {
sendGoogleResponse('I\'m having trouble, can you try that again?'); // Send simple response to user
} else {
if (counter == 3) {
counter = 1;
sendResponse('Custom message');
} else {
counter++;
sendResponse('I\'m having trouble, can you try that again?'); // Send simple response to user
}
}
},
This would work, but idk if this will work for multiple user at the same time, I was thinking to create a storage for storing requests attached by a unique id and have a different counter for each request!
Do you have any better idea of achieving such thing in Dialogflow?
This will not work the way you've designed it. Not quite for the reason you think, but close.
You don't show the rest of your code (that's ok), but the counter variable is probably in a function that gets called each time it processes a message. When that function is finished, the counter variable goes out of scope - it is lost. Having multiple calls at the same time won't really be an issue since each call gets a different scope (I'm glossing over some technical details, but this should be good enough).
One solution is that you could store the variable in a global context - but then you do have the issue of multiple users ending up with the same counter. That is very very bad.
Your solution about keeping a counter in a database, keyed against the user, does make sense. But for this need, it is overkill. It is useful for saving data between conversations, but there are better ways to save information during the same conversation.
The easiest solution would be to use a Dialogflow Context. Contexts let you save state in between calls to your webhook fulfillment during the same conversation and for a specific number of messages received from the user (the lifespan).
In this case, it would be best if you created a context named something like unknown_counter with a lifespan of 1. In the parameters, you might set val to 1.
The lifespan of 1 would mean that you'll only see this context the next time your webhook is called. If they handle it through some other Intent (ie - you understood them), then the context would just vanish after your fulfillment runs.
But if your input.unknown handler is called again, then you would see the context was there and what the value is. If it doesn't meet the threshold, send the context again (with a lifespan of 1 again), but with the value being incremented by 1. If it did meet the threshold - you'd reply with some other answer and close the connection.
By "send the context", I mean that the context would be included as part of the reply. So instead of sending just a string to sendGoogleResponse() or sendResponse() you would send an object that included a speech property and an outputContexts property. Something like this:
var outputContexts = [
{
name: 'unknown_counter',
lifespan: 1,
parameters: {
'val': counterValue,
}
}
];
sendResponse({
speech: "I'm confused. What did you say?",
outputContexts: outputContexts
});

MEAN / AngularJS app check if object already posted

I have thig angularJS frontend and I use express, node and mongo on the backend.
My situation looks like:
//my data to push on server
$scope.things = [{title:"title", other proprieties}, {title:"title", other proprieties}, {title:"title", other proprieties}]
$scope.update = function() {
$scope.things.forEach(function(t) {
Thing.create({
title: t.title,
//other values here
}, function() {
console.log('Thing added');
})
})
};
//where Thing.create its just an $http.post factory
The HTML part looks like:
//html part
<button ng-click="update()">Update Thing</button>
Then on the same page the user has the ability to change the $scope.things and my problem is that when I call update() again all the things are posted twice because literally thats what I'm doing.
Can someone explain me how to check if the 'thing' its already posted to the server just to update the values ($http.put) and if its not posted on server to $http.post.
Or maybe its other way to do this?
I see a few decisions to be made:
1) Should you send the request after the user clicks the "Update" button (like you're currently doing)? Or should you send the request when the user changes the Thing (using ngChange)?
2) If going with the button approach for (1), should you send a request for each Thing (like you're currently doing), or should you first check to see if the Thing has been updated/newly created on the front end.
3) How can you deal with the fact that some Thing's are newly created and others are simply updated? Multiple routes? If so, then how do you know which route to send the request to? Same route? How?
1
To me, the upside of using the "Update" button seems to be that it's clear to the user how it works. By clicking "Update" (and maybe seeing a flash message afterwards), the user knows (and gets visual feedback) that the Thing's have been updated.
The cost to using the "Update" button is that there might be unnecessary requests being made. Network communication is slow, so if you have a lot of Thing's, having a request being made for each Thing could be notably slow.
Ultimately, this seems to be a UX vs. speed decision to me. It depends on the situation and goals, but personally I'd lean towards the "Update" button.
2
The trade-off here seems to be between code simplicity and performance. The simpler solution would just be to make a request for each Thing regardless of whether it has been updated/newly created (for the Thing's that previously existed and haven't changed, no harm will be done - they simply won't get changed).
The more complex but more performant approach would be to keep track of whether or not a Thing has been updated/newly created. You could add a flag called dirty to Thing's to keep track of this.
When a user clicks to create a new Thing, the new Thing would be given a flag of dirty: true.
When you query to get all things from the database, they all should have dirty: false (whether or not you want to store the dirty property on the database or simply append it on the server/front end is up to you).
When a user changes an existing Thing, the dirty property would be set to true.
Then, using the dirty property you could only make requests for the Thing's that are dirty:
$scope.things.forEach(function(thing) {
if (thing.dirty) {
// make request
}
});
The right solution depends on the specifics of your situation, but I tend to err on the side of code simplicity over performance.
3
If you're using Mongoose, the default behavior is to add an _id field to created documents (it's also the default behavior as MongoDB itself as well). So if you haven't overridden this default behavior, and if you aren't explicitly preventing this _id field from being sent back to the client, it should exist for Thing's that have been previously created, thus allow you to distinguish them from newly created Thing's (because newly created Thing's won't have the _id field).
With this, you can conditionally call create or update like so:
$scope.things.forEach(function(thing) {
if (thing._id) {
Thing.update(thing._id, thing);
}
else {
Thing.create(thing);
}
});
Alternatively, you could use a single route that performs "create or update" for you. You can do this by setting { upsert: true } in your update call.
In general, upsert will check to see if a document matches the query criteria... if there's a match, it updates it, if not, it creates it. In your situation, you could probably use upsert in the context of Mongoose's findByIdAndUpdate like so:
Thing.findByIdAndUpdate(id, newThing, { upsert: true }, function(err, doc) {
...
});
See this SO post.
#Adam Zemer neatly addressed concerns I raised in a comment, however I disagree on some points.
Firstly, to answer the question of having an update button or not, you have to ask yourself. Is there any reason why the user would like to discard his changes and not save the work he did. If the answer is no, then it is clear to me that the update should not be place and here is why.
To avoid your user from loosing his work you would need to add confirmations if he attempts to change the page, or close his browser, etc. On the other if everything is continuously saved he has the peace of mind that his work is always saved and you dont have to implement anything to prevent him from loosing his work.
You reduce his workload, one less click for a task may seem insignificant but he might click it many time be sure to have his work save. Also, if its a recurrent tasks it will definitely improve his experience.
Performance wise and code readability wise, you do small requests and do not have to implement any complicated logic to do so. Simple ng-change on inputs.
To make it clear to him that his work is continuously save you can simply say somewhere all your changes are saved and change this to saving changes... when you make a request. For exemple uses, look at office online or google docs.
Then all you would have to do is use the upsert parameter on your mongoDB query to be able to create and update your things with a single request. Here is how your controller would look.
$scope.update = function(changedThing) { // Using the ng-change you send the thing itself in parammeter
var $scope.saving = true; // To display the saving... message
Thing.update({ // This service call your method that update with upsert
title: changedThing.title,
//other values here
}).then( // If you made an http request, I suppose it returns a promise.
function success() {
$scope.saving = false;
console.log('Thing added');
},
function error() {
//handle errors
})
};

How to wait for client response with socket.io?

I'm working on an online, turned based game in order to teach myself Node.js and Socket.IO. Some aspects of the game are resolved serverside. At one point during one of these functions, the server may require input from the clients. Is there a way I can "pause" the resolution of the server's function in order to wait for the clients to respond (via a var x = window.prompt)?
Here's an idea of the code I'm working with:
Server:
for (some loop){
if (some condition){
request input via io.sockets.socket(userSocket[i]).emit('requestInput', data)
}
}
Client:
socket.on('requestInput', function (data) {
var input = window.prompt('What is your input regarding ' + data + '?');
//send input back to the server
socket.emit('refresh', input)
});
Any thoughts?
I don't think that is possible.
for (some loop){
if (some condition){
request input via io.sockets.socket(userSocket[i]).emit('requestInput', data)
/* Even if you were able to pause the execution here, there is no way to resume it when client emits the 'refresh' event with user input */
}
}
What you can do instead is emit all 'requestInput' events without pausing and save all responses you will get in socket.on('refresh',function(){}) event in an array, then you can process this array later. I don't know what your exact requirement is but let me know if that works.
Since you are emitting socket.emit('refresh', input) on the client side, you just need to set up a socket event listener on the server side as well. For example:
io.sockets.on('connection', function (socket) {
socket.on('refresh', function (data) {
console.log(data) //input
});
})
I will also point out, so that you don't run into trouble down the line, that indefinite loops are a big nono in node. Nodejs runs on a single thread so you are actually blocking ALL clients as long as your loop is running.

Categories

Resources