http://qs1969.pair.com?node_id=659611

davies has asked for the wisdom of the Perl Monks concerning the following question:

The problem:

I want to run code until the user hits a key (I don’t care which key). In old fashioned BASIC it’s simple:
count! = 0 DO datain$ = INKEY$ count! = count! + 1 LOOP UNTIL LEN(datain$) > 0 PRINT count!
Running this for about a second results in half a million iterations. But I can’t work out how to do the same thing in Perl.

I have tried:

One way:
use strict; use warnings; use diagnostics; my $count = 0; my $char; until (sysread STDIN, $char, 1) { $count++; } print $count;
Another way, based on Perl cookbook recipe 15.6:
use strict; use warnings; use diagnostics; use Term::ReadKey; my $count = 0; my $char; ReadMode('cbreak'); while ($char eq "") { $char = ReadKey(0); $count++; } print $count;
What happens:

Both respond in the same way. They sit until a key is pressed, and then return 1. I've only waited for up to five seconds, but even if I get multiple iterations by waiting longer, it's not nearly fast enough. I think what is happening is that the code is being optimised too far, but I would welcome clarification on this. I don’t actually need the counter in the finished code – I’m trying to initialise a series of pseudo-random number generators, but that code isn’t relevant to this particular part of the problem. The counter is just there to satisfy me that something is happening.

TIA and regards,

John Davies

Replies are listed 'Best First'.
Re: Problem running code until a user hits a key
by jaldhar (Vicar) on Dec 30, 2007 at 15:45 UTC

    As the POD says, when you use ReadKey with 0 or higher, it does the equivalent of getc(3) which is buffered. You need to use non-buffered mode to do what you want. (Also when your program exits, you need to put the terminal back the way it was. I use an END block to allow for abnormal termination.)

    Try this:

    #!/usr/bin/perl use strict; use warnings; use diagnostics; use Term::ReadKey; my $count = 0; my $char = undef; ReadMode('cbreak'); while (!defined $char) { $char = ReadKey(-1); $count++; } print "$count\n"; # Put the terminal back the way you found it. END { ReadMode('restore'); }

    --
    જલધર

Re: Problem running code until a user hits a key
by jasonk (Parson) on Dec 30, 2007 at 15:50 UTC

    Your second example is close, but you want to call ReadKey with a -1 instead of a 0, to tell Term::ReadKey not to block and wait for a key.

    What you may want to consider though, is that by running in a tight loop like this you are going to suck up CPU time in a big way. For your purposes (collecting random data presumably from keystroke timings), you would probably be better of intentionally blocking and just keeping track of the times between the keystrokes, which would be much less CPU intensive.

    use strict; use warnings; use Term::ReadKey; use Time::HiRes qw( gettimeofday ); ReadMode( 5 ); my $timings_needed = 15; my @timings = (); while ( $timings_needed-- ) { my $char = ReadKey(0); push( @timings, [ $char, gettimeofday() ] ); } for my $time ( @timings ) { print "@{ $time }\n"; } ReadMode( 0 );

    We're not surrounded, we're in a target-rich environment!
Re: Problem running code until a user hits a key
by dsheroh (Monsignor) on Dec 30, 2007 at 16:27 UTC
    Even with Time::Hires's gettimeofday, that doesn't sound like a very effective way to randomly seed things as most users would be prone to hitting a key at a fairly predictable interval if they're just doing it to provide a random seed. (I would expect that the typical user would generally either do it as quickly as possible (consistent time based on user's reflex speed) or wait a fairly consistent N seconds (where N is user-dependent, but generally no greater than 5).)

    I would expect to get greater randomness by using Perl's built-in rand to generate your seed value(s) instead. If rand's pseudo-randomness isn't truly random enough for you and you're running under Linux, reading from /dev/random or /dev/urandom will definitely give you much greater randomness than what you appear to be trying to do.

      I'm not too concerned about getting huge numbers of iterations, just enough for the output to be unpredictable. I'm not sure if my original node made it clear that I'm intending to use multiple PRNGs, for a variety of reasons. I'm also not too worried about sucking up CPU time for the few seconds it will take the user to initialise however many PRNGs are chosen. I would worry about using rand, though, as this would mean that all the PRNGs were seeded in a possibly predictable sequence or set of sequences. I ran into a similar problem about 25-30 years ago, and it left its scars! :-)

      Trying ReadKey(-1) gets me about 100K iterations per second without the PRNGs in place, so the solutions that have been suggested here certainly work in that regard. I'm also interested in jaldhar's comments about needing to reset the terminal - it's something else I didn't pick up from the documentation I read. Actually, it looks as though I must have missed some docs altogether. Whoops.

      Thanks to all for all the help.

      Regards,

      John Davies
        Yep, I did catch that you're seeding multiple PRNGs. My criticism of the method is based on the notion that, since most users will let it run for about the same amount of time each time you do this 'press a key to seed next PRNG' process, you'll end up with a set of seeds which is more predictable than the output of rand. e.g., If the average user has a 0.1 second variance in their response times, then an attacker would only have a range of about 10,000 seeds (based on your "about 100K iterations per second") to check, with those near the middle of the range being much more likely to occur. Granted, each user may have a different range of likely response times, but many users would probably have similar enough ranges that the set of likely seeds would still be smaller than the set of possible results from a mere 16-bit PRNG.