I was watching a documentary about old computers on YouTube recently and it showed a video of an early computer game creating using the command line. This wasn't a text based adventure game, but a game creating using text for the graphics running as a program on the command line. This got me thinking that creating something like this should be possible using PHP. If it was possible on a 30 year old computer then surely it's possible to get PHP to do it, right? I thought it might be interesting to create a series of posts showing how to put this together.
The first part of the puzzle is to solve how to detect input from the keyboard on the command line, in real time. The key to this is using the php://stdin stream wrapper, which is a handy wrapper around incoming data for a PHP application. I've written about php://stdin and other PHP stream wrappers before if you are interested. What we need to do is open a read only connection to the php://stdin input and then read from it using something like fgets().
That's not the whole story though, we also need to make sure that the stream resource we have opened doesn't block the PHP application when we read from it. This is done by calling the stream_set_blocking() function and passing in the stream we want to be non-blocking as an argument.
Now, when we read from the stream using fgets() we aren't going to causing any locking of the program. Without this in place the program would just hang whilst we are trying to read from the input.
$stdin = fopen('php://stdin', 'r');
// Set the stream to be non-blocking.
stream_set_blocking($stdin, 0);
$keypress = fgets($stdin);
This isn't the end of the story though. We also need to tell the command line interface to pass the keyed input directly to the script without modification. This is done by running the 'stty cbreak -echo' command through the system() function. The system() function just passes the string entered directly to the command line, essentially running the program.
The 'stty cbreak' command changes the mode of the terminal to cbreak, sometimes called rare mode. In this mode, every character is passed directly to the terminal application after it is entered, instead of after the enter button is pressed. The -echo flag means that this will be done silently, instead of the characters being passed to the terminal output. Without this in place we would need to press enter after every keypress to allow it to be registered.
system('stty cbreak -echo');
With all that in place we now need to translate some of our keypresses to something useful. Most keys will translate into their usual counterparts (e.g. pressing 'a' will print 'a') but we also need to translate some of the system keys that may get pressed. This is keys like the arrow keys, enter, escape, that kind of thing. The following function will detect some of these common keys and translate them into strings instead of character codes.
function translateKeypress($string) {
switch ($string) {
case "\033[A":
return "UP";
case "\033[B":
return "DOWN";
case "\033[C":
return "RIGHT";
case "\033[D":
return "LEFT";
case "\n":
return "ENTER";
case " ":
return "SPACE";
case "\010":
case "\177":
return "BACKSPACE";
case "\t":
return "TAB";
case "\e":
return "ESC";
}
return $string;
}
With that in place we can create a small script that will listen to incoming keypresses and report on what keys have been pressed. If the key happens to be a special key then it will be translated into a string representation of that key.
$stdin = fopen('php://stdin', 'r');
stream_set_blocking($stdin, 0);
system('stty cbreak -echo');
while (1) {
$keypress = fgets($stdin);
if ($keypress) {
echo 'Key pressed: ' . translateKeypress($keypress) . PHP_EOL;
}
}
The following shows this script in action and after pressing some keys.
$ php detectkeys.php
Key pressed: p
Key pressed: h
Key pressed: p
Key pressed: SPACE
Key pressed: g
Key pressed: a
Key pressed: m
Key pressed: e
Key pressed: LEFT
Key pressed: UP
Key pressed: RIGHT
Key pressed: DOWN
Key pressed: ENTER
Key pressed: BACKSPACE
Key pressed: ESC
Key pressed: TAB
We now have a mechanism to listen to keys being pressed, which we can use to handle the controls of a game. In the next post I will look at setting up a game using this control method and rendering the output on the command line.
If you want to see this code in full then I have created a GitHub gist that you can download and run.
Add new comment