PDA

View Full Version : Keys in Order


i8bit
01-01-2014, 09:02 PM
I am trying to make an action occur only if the right keys are hit in order.
For example: you have to hit A, S, D, F, in that order to trigger the action.

Blah64
01-01-2014, 09:09 PM
Cool

cbk1994
01-02-2014, 12:10 AM
What do you have so far? Post some code.

Probably the simplest approach is keep track of the player's current position in a combo. When they hit the right key, increase the position. When they hit a wrong key, reset their position. If they hit the end of the combo, they've hit all the keys in the right order, so do whatever action.

But there are lots of potential approaches here, so post some code so we know where you're getting stuck.

100Zero100
01-14-2014, 04:07 AM
What do you have so far? Post some code.

Probably the simplest approach is keep track of the player's current position in a combo. When they hit the right key, increase the position. When they hit a wrong key, reset their position. If they hit the end of the combo, they've hit all the keys in the right order, so do whatever action.

But there are lots of potential approaches here, so post some code so we know where you're getting stuck.

I disagree about simplest. In your method, the script would have to simultaneously track player's progress through multiple combinations. If one combination was QWERTY and another was ERGAF and another was ROCKSTAR, then a player who typed QWER needs to be recognized as being 4/6 done with QWERTY, 2/5 with ERGAF, and 1/8 with ROCKSTAR. That's more complex.

Without testing/off the top of my head, I believe the simplest, most efficient, and most editable method would be something like as follows:


//#CLIENTSIDE
const resetTime = 2;
function onCreated() {
this.codes = {
"qwerty",
"asdf",
"zxcv",
"etc"
};
this.text = "";
}
function onKeyPressed(keycode, key) {
this.text @= key;
for (temp.ending: this.codes) {
if (this.text.ends(ending))
// Code triggers
}
cancelEvents("reset");
scheduleEvent(resetTime, "reset");
}
function onReset() {
this.text = "";
}


The example reset time there is 2 seconds. That is an individual 2 seconds, however. That would be best in the scenario where you give the player two seconds between every keystroke. That means, for "asdf", they could hit A, wait 1.9s, hit S, wait 1.9s, hit D, wait 1.9s, and hit F, and it would register as the ASDF ending.

If you instead wanted to give a player 2 seconds from first-letter to last-letter to type the full combo, it would be:


//#CLIENTSIDE
const resetTime = 2;
function onCreated() {
this.codes = {
"qwerty",
"asdf",
"zxcv",
"etc"
};
this.text = "";
}
function onKeyPressed(keycode, key) {
this.text @= key;
for (temp.ending: this.codes) {
if (this.text.ends(ending))
// Code triggers
}

scheduleEvent(resetTime, "remove");
}
function onRemove() {
this.text = this.text.substring(1);
}


In this scenario, typing a letter adds it to the string. In 2 seconds, the scheduleEvent will finish and the first letter (that letter) will be removed from the string. Thus, doing A - wait 1.9s - S - etc method won't work, because 0.1s after typing S, the A would be removed from the combo.

Of course, scheduleEvents() like this tend to mess up sometimes if a player over-spams (notably when they hit keys with both hands at the same time rapidly). I believe it's due to the scheduleEvent() limit being exceeded. However, in this scenario that wouldn't actually hurt our effect. What would occur is some letters would be added to the this.text string that would never get removed. While unideal, that's pointless: the script only cares about how it ends. While in theory you could possibly get it to "glitch" on several letters that actually matter, due to the chaotic nature of "random button mashing" it's extremely unlikely.

Hezzy002
01-14-2014, 04:25 AM
I disagree about simplest. In your method, the script would have to simultaneously track player's progress through multiple combinations. If one combination was QWERTY and another was ERGAF and another was ROCKSTAR, then a player who typed QWER needs to be recognized as being 4/6 done with QWERTY, 2/5 with ERGAF, and 1/8 with ROCKSTAR. That's more complex.

this is the worst idea ever i like his idea better because then like if u want to pres the bubuttons faster it wtill still work even though it didnt work last time because u didnt press the buttons in the 2 second window like wht if i ttakes u a little longer than 2 seconds not everyone is really fast at typing cuz i sure aint lmao im a really slow typer. anyway cuz lye lik what if u r rr typing and thenn u start sometimes it wont be responsive u know?

100Zero100
01-14-2014, 04:34 AM
this is the worst idea ever i like his idea better because then like if u want to pres the bubuttons faster it wtill still work even though it didnt work last time because u didnt press the buttons in the 2 second window like wht if i ttakes u a little longer than 2 seconds not everyone is really fast at typing cuz i sure aint lmao im a really slow typer. anyway cuz lye lik what if u r rr typing and thenn u start sometimes it wont be responsive u know?

You trolling me?

In my first suggestion, I said that the reset timer (which could be defined as a number larger than 2 seconds) would allow the player that amount of time between EACH letter. So you could, for example, with a 2 second reset, do "A" (2 seconds) -> "S" (2 seconds) -> "D" (2 seconds) -> "F" and it'd work.

Whereas I also offered the second suggestion *if the asker would prefer it* which would make the player enter the whole word within the reset window.

By no means am I trying to make the system into a Mortal Kombat Fatality, but in the first script I posted, something like a 1-1.5-second reset would be ideal (you'd have 1-1.5 second between letters). While in the second script I posted, something like a 6-second reset would be ideal (you'd have 6 seconds to enter in the entire code).

It's just the best way to do it, no doubt about it.

Chompy
01-14-2014, 10:21 AM
...

Your code could be optimized further though, instead of having to do a loop at every keystroke the player does.

Also, there's no ending the loop if there's a match as far I as I see. Could have been left out intentionally though, I dunno. Combo build ups maybe?

100Zero100
01-14-2014, 04:43 PM
Your code could be optimized further though, instead of having to do a loop at every keystroke the player does.

Also, there's no ending the loop if there's a match as far I as I see. Could have been left out intentionally though, I dunno. Combo build ups maybe?

Could it? I can't think of a way that you could avoid looping over the matches on every keystroke. I figured it was a small deal though -- a loop of about 3-4 arrays on a keypress seemed minor, but I agree -- if it can be done better, it should be!

I intentionally left out the 'end' on match -- not to have "multiple" combos in 1 stroke, but because I figure if you do 1 combo now, you probably want to do another combo in like 5 seconds.. but I did assume he wanted to have some kind of "delay" period. I left that all for him to decide in the "// Code triggers" portion.

How could you not loop over the array on every keystroke? I'm actually interested in that. I mean sure you can loop over every 3-4 keys or you could loop every 0.5 seconds (using .pos()>=0 instead of .ends()) up to the reset delay (in the first format).

But I figured all of those would feel a lot less responsive (you finish your combo, 0.5s later the script triggers after you've pressed 3 other keys) and it wouldn't give it all that much in efficiency.

I mean one thing you can do is if you planned on having all of your "combos" to be EXACTLY 4 characters, you could do something like:

(in the second format):

//#CLIENTSIDE
const resetTime = 2;
const codeLength = 4;
function onCreated() {
this.codes = {
"qwerty",
"asdf"
"zxcv",
"etc1"
};
this.text = "";
}
function onKeyPressed(keycode, key) {
this.text @= key;
temp.testCode = text.substring(text.length() - codeLength - 1, codeLength);
if (testCode in this.codes) {
// Code triggers
}

scheduleEvent(resetTime, "remove");
}
function onRemove() {
this.text = this.text.substring(1);
}

And that seems like a slight gain in efficiency (since the loop over the array still happens (that's how 'in' works!) but in a hardcoded and more efficient manner.

I suppose you could even change "const codeLength" into "this.codeLengths = {length1, length2};" and loop an amount of times equal to your code lengths in the same manner, to keep looping down. In that scenario, you'd also want to partition your "combos" by code size, that way the script would loop only over the code-lengths relevant to your current size (eg, this.codes.3 = {"asd", "etc"}; this.codes.4 = {"qwer", "lolz"};).

In theory though that is definitely a gain in efficiency if you had like 20-50 combos that were all 3, 4, or 5 characters. It reduces looping to 1-3 loops rather than 20-50, and then it would need to sub-loop (via 'in' - hardcoded - more efficient) for the difference.

Still, even in that scenario, with the gain of efficiency there, having to edit like 3 arrays every time you want to add a combo is, in my opinion, not worth that trade-off.

Keep in mind, the goal isn't always to make the crazily most efficient script of the year. Sometimes you just want the script to be so clean, precise, and simple that any-old beginner could pop in and add his combo to the list, or whatever.

I will definitely admit that there are multiple approaches from this angle, with trade-offs between efficiency and editability. I still stand by my original claim that doing it the other way (keeping track of the character's position in each combo) is much worse: you would need 3 arrays (1 for the combos, 1 zeros-array equal in size to the first, and 1 keeping track of the player's current progress in each combo).

Were you referencing a more efficient way then I mentioned? If so, I would love to learn it!

Chompy
01-15-2014, 12:48 AM
If you can pinpoint things directly, lets say variable name being the same as the key order, why just not check if it exists?

Lets take a modular example, with great possibilities when it comes to expanding the concept as a system.

http://pastebin.com/A21Yc1sA

I had to pastebin it, since I got an Access Denied when trying to post the code.

I wrote it without testing it, but the general idea should be there. I also made it much bigger than it had to be, but it was fun. Could easily become something bigger, it's quite flexible if some more changes are done to it. Also made it so you have to enable the possibility of doing combos.

This code is not done at all, I just wrote it for this post to illustrate an example.

Torankusu
01-15-2014, 03:01 AM
If you can pinpoint things directly, lets say variable name being the same as the key order, why just not check if it exists?

Lets take a modular example, with great possibilities when it comes to expanding the concept as a system.

http://pastebin.com/A21Yc1sA

I had to pastebin it, since I got an Access Denied when trying to post the code.

I wrote it without testing it, but the general idea should be there. I also made it much bigger than it had to be, but it was fun. Could easily become something bigger, it's quite flexible if some more changes are done to it. Also made it so you have to enable the possibility of doing combos.

This code is not done at all, I just wrote it for this post to illustrate an example.

nice styling.


temp.check = ((this.testcheck!=false)
? this.testcheck(this.test)
: this.check(this.test)
);


really. i know people that do this on the same line, but I can read it easier on separate lines (though it takes more discipline while writing it up because you might overlook a ) or } [for arrays, etc].

Plus, i don't have a fancy widescreen monitor so I tend to write in really thin-width windows. :P

100Zero100
01-15-2014, 08:40 AM
If you can pinpoint things directly, lets say variable name being the same as the key order, why just not check if it exists?

Lets take a modular example, with great possibilities when it comes to expanding the concept as a system.

http://pastebin.com/A21Yc1sA

I had to pastebin it, since I got an Access Denied when trying to post the code.

I wrote it without testing it, but the general idea should be there. I also made it much bigger than it had to be, but it was fun. Could easily become something bigger, it's quite flexible if some more changes are done to it. Also made it so you have to enable the possibility of doing combos.

This code is not done at all, I just wrote it for this post to illustrate an example.

I have a lot of respect for the amount of effort you put into that example. However, I need to respectfully disagree.

To begin, I assume you put this._ instead of just this. because their typing something like "function()" would have done "this.function()" - and makevar would cause that to retrieve the function's value. Or typing 'x' would return this.x in that scenario. In yours, this._x would be safe, as would this._function(). I get that.

However, I feel if you wanted to test for a variable without just outright doing this.(@var) due to potential exploit or mistake, then why not do something like this.code.(@var)?

That would retain its security/lack of false positives, without having to use makevar(" " @ " ")-type of stuff. What do you think of that?

In addition, I see an overarching flaw with the entire concept there.

If a player typed "dreanm" over 0.5 seconds (fast typist), it would fail. I'm sure they'd quickly realize "oops, I hit N when reaching for M!" and quickly re-type "dream" over 0.5 seconds. In such a scenario, the player basically spend about 1-1.5 seconds typing "dreanmdream" -- and it wouldn't even function. As a player, that would lead me to believe that perhaps "dream" is not the correct answer.

Since a player wouldn't KNOW that the reset time is 2 seconds, it could cause problems. If they went to type "dream" but typoed and put "dres" and then realized "ugh I accidentally hit S" and then only waited 1.5 seconds and began typing "dream" over, the script would probably intercept them around the "a", resetting their text back to nothing, and then receiving "am."

As an aside -- in that scenario, it looks like your script would actually interrupt them when they hit the "a" to perform a grab. Is that intended? Or perhaps my glance was a bit too cursory. If so, I apologize. But if the "a" didn't perform a grab in this scenario -- what would? Pressing "a" and then waiting 2 full seconds for it to register?

Back to the original point: so the player now has "am" and realizes the script didn't work. They might think "Hmm I'll try again" and immediately start typing "dream" -- except now the code sees they've put "amdream." Doesn't work again.

By now, the player probably gives up the idea that it's "dream." Sure, a smart player might take his hands off the keyboard, wait 5-10 seconds, and re-type it with a "clean slate" so-to-speak, but not all players will be that keen to how the system might work.

Do you see what I am saying, regarding this user-unfriendliness? The reason I had my "freeflow" style (which you referred to as "combos") is so that a player could put 0295892058025asdf258058 and still get credit for "asdf" as soon as that final "f" was hit, while putting "qawesedewfs" would not register "asdf" because "asdf" is not in one place altogether.

I never intended for "combos" to work the way you seem to understand, where "asdfqwer" should do some special function (equal or better than "asdf" + "qwer"). In where I put "// code triggers" I was implying that when the "f" was typed of "asdf," the script would stop reading the player's text, begin a fixed delay period, execute the function associated to the "asdf" code, and finally resume reading the player's text when said function terminated. I simply wanted the "freeform" continuity of typing for these such mistakes and typoes! If I may, I'd like to add that I believe the "combos" idea is a neat one, it just wasn't my idea.

When I saw you explain your method (before I saw your script), this is how I interpretted your explanation. Let me reiterate that I think it's a great idea of a method to do this, and I appreciate you bringing it to my attention.

//#CLIENTSIDE
const prefix = "code";
const remove = 2;
const codeLength = 4;
enum codes {
"asdf" = true;
"qwer" = true;
"zxcv" = true;
"etc1" = true
};

function onKeyPressed(temp.keycode, temp.key) {
this.text @= key;
temp.test = text.substring(text.length() - codeLength - 1, codeLength);
if (codes.(@text)) {
this.trigger(prefix @ text);
}
scheduleEvent(remove, "remove");
}
function onRemove() {
this.text = this.text.substring(1);
}
function onCodeASDF() {
// This would occur if a player typed asdf, even if they typed "qsasdfrwg" -- the "rwg" wouldn't register, "asdf" would trigger and begin a function. Reading would resume (with no text) once the "asdf" function concluded)
}

In THAT interpretation, I could see your idea being highly efficient. Even still, due to the limitation of '4', I don't think it's worth it. Also, I have doubts that this system is more efficient than the 'in' method I discussed in my second post (suffers same limitation).. And outside of that interpretation, I don't see a gain of efficiency.

I know that you said that my original method involved a loop, but how is that less efficient than what you posted? Because switch()case: is that much more efficient due to being slightly more low-level than a loop? That's the same order of magnitude of checks (a system with 50 different codes: in my system, it's a loop of 50. In your system, it's 50 case: checks. Same order of magnitude. 50 checks for both!). I think that's splitting hairs at that point, and there's no denying that for such a small efficiency gain, my original method was far more readable and editable, only requiring an entry change into an array at the top (+ function definition), as opposed to having to add a new "case:" into the middle (+ function definition). I will definitely give you credit where credit is due, though, and say that your critique made me consider an "array"-based system, which I truthfully believe to be the most efficient system.

Final note: I still think my original script to be the most readable, and retains high editability even to non-scripters.