0

I'm converting an old terminal-mode text game to JavaScript.

The gameplay conversion is simple and nearly done. But a part of the UI -- getting user input -- is proving so difficult that I'm turning to the experts on StackOverflow for help. I have been through SO exhaustively and found similar answers but nothing directly applicable.

One of my goals is to preserve the game's 1970s-era input style:

  1. All text is displayed on a single screen (no pop-up dialogs or isolated/visible input boxes).
  2. User input is obtained by displaying a prompt message, followed by waiting for the player to enter a response.
  3. The individual character of the user's response are displayed on the screen.
  4. The characters entered can be removed in reverse order by pressing the Backspace key.
  5. The number of characters that can be entered is limited.
  6. All other game processing is suspended until the user presses the Return key.
  7. When the Return key is pressed, all characters entered are returned to the calling function as a string.
  8. When the result string is obtained by the calling function, processing in the main gameplay code resumes.

I have successfully figured out steps 1 and 3-5. I create an Input element in HTML, move it offscreen in CSS, and set the focus to that element. When a key is pressed, if it's an alphanumeric I use appendChild() to add it as a text node to the content element so that it's displayed on the main screen; if it's the Backspace key I remove the tail child node to remove the character from the screen; and if it's the Return key I copy the text from the input field to a local variable to return it to the calling program and clear the input field.

After some effort, the code to print and backspace characters in a content area is working. What I have been unable to figure out is how to suspend other processing until the user has typed some keys and hit Return.

I've spent the last three days trawling StackOverflow and every other site I could find that had a reference to this kind of thing, plus trying every combination of semaphoring I could think of myself. A prompt dialog or visible input box fails requirement #1. A naive while-loop does halt processing, but it also prevents keypresses from being recognized. Timers and intervals risk missing keypresses. And I'd rather avoid yield() in order to support older browsers. (I'd like to preserve cross-browser compatibility as far as possible.)

Similar questions have generated answers that reference asynchrony (via callback), but that is exactly what I do not want -- I want the main gameplay code to stop running, but still allow keypresses to be detected, until the user presses the Return key, after which the main gameplay loop can resume.

Is there any way to accomplish this? I'm open to some rethinking of how the main gameplay loop is structured if there is no relatively clean way to simulate the prompt dialog's modal text entry in JavaScript.

Note: If sample code is offered, examples in straight HTML/CSS/JavaScript would be much more helpful to me than jQuery. Thanks!


Edit: The part of the program that's working is creating a listener for keypresses and displaying alphanumeric characters to the content area:

form.addEventListener("keydown", function (e) {
    if (e.keyCode == 13) {
        istr = inputBox.value; // save user's text as a string
        inputBox.value = ""; // clear input element
    }
    else if (e.keyCode == 8)
        backspace(); // remove last char from content area
    else if (e.keyCode >= 48 && e.keyCode <= 90)
        printCharST(String.fromCharCode(e.keyCode)); // put char in content area
    inputBox.focus(); // bring focus back to input textbox
}, false);

This is not the part of the code I'm asking about. (I'm including it here so that it's clear that I do have code to detect key presses and operate on them.)

The part I'm having trouble with is the calling program -- how do I get it to suspend until the user presses Return, and I put their string into istr and return it to the caller who's been waiting for user input? Simply telling the caller to repeatedly poll istr (or any other flag variable set by the key listener) until it's not empty chews up so much CPU that it prevents key presses from being recognized.

I'll be happy to provide any additional information that would help clarify what I'm trying to achieve. Also, if there are any specific comments on how this question could be better formatted, I'll gladly revise it.

Bart Stewart
  • 101
  • 8

1 Answers1

1

The simplest, quickest, easiest-to-implement approach would be to use... a callback handler. Rather than explain it, it's much easier to see it in action:

  • Create an onkeydown handler (see this answer for how to do that: https://stackoverflow.com/a/4929676/2167545)
  • In that onkeydown handler create a function that will take the most recently clicked key and append it to the input area.
  • Have that function return a boolean representing whether the key was 'return' (true) or not (false)
  • If that function returns true, hand the current input to an interpreter and step your game
    • this would involve printing more text, updating enemies, conversations, etc.

The reason this works is because of the nature of the game as you've described it. The game must stop and wait for input every time the player has an option. Each of these pauses naturally splits the game up into pieces that have well defined operations. In a conversation, for example, these operations would manage the creation of NPC responses to player input. This same example can be extended for fight scenes, descriptions, navigation, etc. You can even add timeouts (if you're careful) so that the player has to type quickly and accurately to continue. These timeouts would be impossible if you suspended all operations except user input for the duration between a prompt and the return key.

EDIT: The problem still seems to be in the event handler you showed. Currently, all it does is listen for input and assign that input to a value. This implies that you have a main game loop somewhere in your code that is checking the value of istr every tick and then responding to it. This design is not useful for your purposes and is inefficient.

Given the nature of the game you're trying to write there are going to be long periods of inactivity where the game is just waiting for the user to finish typing in his response. This is followed by a brief period of activity when the user hits the return key. The main game loop approach is too much for this. A much simpler design can increase the simplicity and robustness of your code. The design I specified above is much better for this than the game loop approach because it's control flow matches the description in the last paragraph.

Example implementation

EDIT: If you have a game loop because you need to do something with the input while the user is typing, that can also be added easily. The fiddle has been updated with comments and an example point of expansion.

Community
  • 1
  • 1
Mikayla Maki
  • 473
  • 6
  • 18
  • Thank you! I've provided additional information about my question there, including code that shows I'm already using a listener for keydown events and processing them. The difficulty I'm having is not there, but with the calling code: what can it be doing while the listener code is waiting for keypresses that isn't just a while() loop? – Bart Stewart May 24 '14 at 16:37
  • Many thanks, @Trenton Maki -- this is in line with what I have reluctantly been thinking. Rather than a straight conversion, I will need to restructure the flow so that the main loop is the user input code and the correct part of the gameplay code is executed conditionally. This is a bizarre inversion of just getting a piece of user input! (Since the caller always knows the type of text to be returned.) But it seems to be the only option. Your code example is an excellent template for that approach -- I greatly appreciate the assistance. – Bart Stewart May 24 '14 at 20:25
  • Why do you need a main loop at all? You can just use JS's event handling and scrap the loop altogether. My example should have illustrated that. – Mikayla Maki May 24 '14 at 21:42
  • I use the term "loop" because the game is not a linear experience. The core of the game has the player type a number for an option. Then text is displayed, random numbers are generated, and the user is asked for additional input of various kinds. Once that's been processed, and if the game has not ended, the game returns to asking the player to enter another number from the command list. This is what I mean by "loop" -- it's also why pure event handling is not a great fit for this particular game. But it's my task to rethink that structure, which I can do now, thanks to your advice. – Bart Stewart May 24 '14 at 22:19
  • 1
    That's interesting. I've never heard of a live action text based game. A main game loop would definitely be needed but you should still be able to use events for user input. You could also create a playerInput event that fires every time the user finishes entering input. That way the rest of your code could take advantage of both the main game loop and the callback handlers. This might help: http://www.sitepoint.com/javascript-custom-events/ as would a good understanding of the Observer pattern: http://en.wikipedia.org/wiki/Observer_pattern. Good luck with the game! – Mikayla Maki May 24 '14 at 22:27