9
\$\begingroup\$

In 1984, Michael Crichton wrote a security program in BASIC that was published in Creative Computing magazine. The program would ask the user to type a phrase of her choice, record the intervals between the keystrokes, then ask her to retype the phrase. If the timings differed by too much, the program would identify the user as an imposter.

Your assignment: create a version of Crichton's program in the language of your choice.

Rules:

  1. Phrases for communicating with the user ("Please type the key phrase," "Please type the key phrase again," etc.) count as one byte each regardless of actual length. This is for user communication only, do not try to hide program code in the strings.

  2. The pass/fail test should be based on the average absolute value of the percentage variances from the original intervals. If the strings don't match, either return fail or allow the user to try again, at your discretion.

  3. The key phrase should not allow a null string. In the event the key phrase is too long for your string datatype, either truncate or disallow and start over, at your discretion.

  4. The sensitivity of the test (the threshold for the pass/fail test) should be adjustable in the source code.

  5. I originally provided a bonus of 20% of total byte count if your source code can be formatted to recognizably resemble a dinosaur. It has been pointed out that this is highly subjective and perhaps more appropriate for a popularity contest, so I have removed this bonus. However I still heartily encourage dinosaur formatting, and if you format your code to look like a dinosaur, you can deduct any purely cosmetic comments, linebreak or whitespace characters from your byte total.

  6. Shortest byte count wins, subject to string-length and dinosaur formatting adjustments.

Note that my specification above does not exactly match the operation of Crichton's code, copies of which may be found online. Follow the spec, don't try to clone the original.

\$\endgroup\$
  • 5
    \$\begingroup\$ "It's Michael Crichton, so subtract 20% from total byte count if your source code can be formatted to recognizably resemble a dinosaur." - umm... no. This rule is too subjective. Please remove. Other than that, please go ahead. \$\endgroup\$ – John Dvorak Nov 11 '14 at 20:24
  • 4
    \$\begingroup\$ @JanDvorak I don't think its "too" subjective. Its a fairly easy call to call some ASCII art as dino or not \$\endgroup\$ – Optimizer Nov 11 '14 at 20:27
  • 3
    \$\begingroup\$ @Optimizer Not in all cases. Does the greek letter lambda look like a dinosaur? I'm pretty sure it does. \$\endgroup\$ – John Dvorak Nov 11 '14 at 20:32
  • 3
    \$\begingroup\$ A few other minor comments: Does "Please type the key phrase" count as 1 byte, or does only the phrase count and the quoted phrase counts as 3 bytes (", phrase, ")? Is it intentional that a much longer interval and a much shorter interval will "cancel out" and become even again? Does the program have to verify that the two key phrases match? \$\endgroup\$ – Doorknob Nov 11 '14 at 21:55
  • 3
    \$\begingroup\$ It was in June 1984. You can see the original program here. \$\endgroup\$ – squeamish ossifrage Nov 12 '14 at 12:02
9
\$\begingroup\$

Ruby, 171 167 157 bytes

require'io/console';t=Time;f=->a{loop{x=t.now;STDIN.getch==?\r?break: a<<t.now-x};a};p"Please type the key phrase";f[r=[]];p"Please type the key phrase again";p r.zip(f[[]]).map{|x,y|(1-x/y).abs}.reduce(:+)/r.size>0.2

Outputs true if average variance is above 20%, otherwise outputs false.

Dinosaur ASCII art attempt:

(_=/\
  \ \
   \ \
    \ \              _...---..__
     \ \          .∕` #{t=Time} `\._
      \ \      .∕ #{z='io/console'} `\.
       \ \.__.∕  #{require z;s=STDIN} `\.
        \ #{p'1:';f=->a{loop{x=t.now;#   \.
         s.getch==?\r?break: a<<t.now-x;# `\.
          };a};f[r=[]];p'2:';p r.zip(f[[]])#\  
           .map{|x,y|(1-x/y).abs}.reduce(:+)#|
            .fdiv(r.size)>0.2}###########\   \
            `-._    ,___...----...__,   ,__\  \
                |   |_|           |_|   |    \ \
                |___|               |___|      \\/)

Ungolfed:

require 'io/console' # so we can read one char at a time

t = Time

f = ->(a) {
  loop {
    x = t.now # record start time
    break if STDIN.getch == ?\r
    a << t.now - x # push (start time - end time) into array
  }
  a
}

puts "Please type the key phrase"
f[r = []] 

puts "Please type the key phrase again"

# interweave timing arrays, compute variances, sum elements
# then divide by array length. Check to see if average
# is greater than threshold (0.2)
p r.zip(f[[]]).map { |x,y| (1-x/y).abs }.reduce(:+) / r.size > 0.2

require 'io/console' could be removed when run in some Ruby REPLs, as the library is already loaded.

\$\endgroup\$
4
\$\begingroup\$

Java 768 bytes

what? Java? for code golf?

This is probably the worst thing to do, but I tried it anyway.

It displays any messages in the console window, but the actual typing occurs in the JTextField. Not exactly nice looking. Oh, and to save 5 bytes, you have to resize the JFrame yourself. Also, it doesn't check for string correctness the second time. Not sure if that is against the specs.

To Use:

Type your key in the textfield.

Don't press enter, go to the console and type something. It will display another message

Type the same thing in the textfield (which should now be cleared).

Go to the console and press something again. It will display whether you are an intruder or not.

ungolfed:

import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

public class CrichtonsMousetrap {
    public static void main(String[]a){
        new CrichtonsMousetrap();
    }
    long start;
    List<Long>elapsed = new ArrayList<>();
    List<Long>e2;
    public CrichtonsMousetrap(){
        JFrame f = new JFrame();
        f.setSize(199,70);
        f.setVisible(true);
        JTextField t = new JTextField();
        System.out.println("please type in the key phrase.");
        f.add(t);
        t.getDocument().addDocumentListener(new DocumentListener(){
            @Override
            public void changedUpdate(DocumentEvent e) {}
            @Override
            public void insertUpdate(DocumentEvent e) {
                long r = System.nanoTime();
                if(start!=0){elapsed.add(r-start);}
                start=r;}
            @Override
            public void removeUpdate(DocumentEvent e) {}            
        });
        Scanner s = new Scanner(System.in);
        s.next();
        System.out.println("please type that again!");
        e2=elapsed;
        elapsed=new ArrayList<>();
        start=0;
        t.setText("");
        s.next();
        double sum=0;
        for(int i=0;i<e2.size();i++){
            sum+=Math.abs(1-elapsed.get(i)/(double)e2.get(i));
        }
        System.out.println("your average percent error was " + sum/e2.size());
        double okLimit = .2;
        System.out.println(sum/e2.size() < okLimit ? "you're ok":"INTRUDER!");
    }
}

golfed:

import java.util.*;import javax.swing.*;import javax.swing.event.*;class q{static long p;static List<Long>y=new ArrayList<>(),o;public static void main(String[]a){JFrame f=new JFrame();f.setSize(0,0);f.setVisible(true);JTextField t=new JTextField();System.out.println("please type in the key phrase.");f.add(t);t.getDocument().addDocumentListener(new DocumentListener(){public void changedUpdate(DocumentEvent e){}public void insertUpdate(DocumentEvent e){long r=System.nanoTime();if(p!=0){y.add(r-p);}p=r;}public void removeUpdate(DocumentEvent e){}});Scanner s = new Scanner(System.in);s.next();System.out.println("please type that again!");o=y;y=new ArrayList<>();p=0;t.setText("");s.next();double b=0;for(int i=0;i<o.size();b+=Math.abs(1-y.get(i)/(double)o.get(i++)));System.out.print(b/o.size() < .25 ? "you're ok":"INTRUDER!");}}
\$\endgroup\$
  • \$\begingroup\$ There is no way to set TTY in raw mode from java (unless you are ready to use JNI). So I understand why you need a JFrame. But really, this is the least user-friendly program I've seen in ages :-) I am not sure if I want to upvote or downvote this answer. \$\endgroup\$ – coredump Nov 12 '14 at 20:30
  • \$\begingroup\$ I upvote for the sheer amount of user unfriendliness (ist that even a word?). It's basically art. \$\endgroup\$ – Ingo Bürk Nov 13 '14 at 6:27
  • \$\begingroup\$ I believe this could be golfed more by making the class extend JFrame, so you wouldn't need f. \$\endgroup\$ – PurkkaKoodari Nov 14 '14 at 8:41
3
\$\begingroup\$

Scala REPL 233

def l:Stream[(Int,Long)]=(Console.in.read,System.nanoTime)#::l    
def m={
    println("Enter");     
    l.takeWhile(_._1!=13).map(_._2).toList.sliding(2).map(a=>a(1)-a(0))
}
val k=m.zip(m)     
k.map(a=>Math.abs(a._2-a._1)/(a._1.toDouble*k.length)).sum<0.2

With all the spacing removed, you have:

def l:Stream[(Int,Long)]=(Console.in.read,System.nanoTime)#::l;def m={println("Enter");l.takeWhile(_._1!=13).map(_._2).toList.sliding(2).map(a=>a(1)-a(0))};val k=m.zip(m);k.map(a=>Math.abs(a._2-a._1)/(a._1.toDouble*k.length)).sum<0.2

Which I'm sure somebody more talented than I am could make into a dinosaur!

Brief explanation:

The l method read characters and keeps tract of the nanoTime of when each character was typed.

The m method prints "Enter", breaks the l method upon hitting enter (character 13), then maps it to just the nanoTimes, and then gets the time intervals between each character.

The next 2 lines reads in 2 strings, zips them, then finds the average absolute value of the percentage difference between the second intervals and the first, and finally prints whether or not this average was less than 0.2.

\$\endgroup\$
3
\$\begingroup\$

HTML, JavaScript (ES6), 328

Total bytes count of the code is 402 bytes and the messages to interact with the user:

"Valid User"
"Imposter alert!!"
"Please Enter the Key again"
Please Enter the Key

are total 78 bytes, so total score => 402 - 78 + 4 = 328

Run the snippet below in a latest Firefox and type the key in the input box followed by Enter key.

The code checks that both the entered and re-entered keys are same, (prompts for re-entering again if not), calculates the average absolute difference percentage and checks if it is less than the the value of the variable V

<a id=t >Please Enter the Key</a><input id=f /><script>V=.3,a=[],i=0,s=b="",q=0
c=_=>(j=0,_.slice(1).map(v=>j+=Math.abs(v)/i),alert(j<V?"Valid User":"Imposter alert!!"))
r=_=>(a=[],i=0,t.textContent="Please Enter the Key again",f.value="")
f.onkeyup=_=>_.keyCode==13?q++?s==f.value?(A=a,B=b,A=a.map((v,i)=>v-A[i-1]),c(b.map((v,i)=>(v-B[i-1]-A[i])/A[i]))):r():r(b=a,s=f.value):a[i++]=Date.now()</script>

\$\endgroup\$
3
\$\begingroup\$

C, 154 (86+68 for flags)

d[99],i,a,b;main(x,y){P"Please type the key phrase"W(E-13)U,x=y;U;P"Please type 
the key phrase again"W(a<i)E,b+=abs(Y-Z)*99/Z,++a,x=y;b<a*9||P"No cake for imposters");}

Compile with -DY=(y=clock())-x, -DZ=a[d], -DE=getch(), -DW=);while, -DU=i++[d]=Y and -DP=puts(. Newlines added for presentation purposes and may be removed (byte count given is without).

Ungolfed+comments:

d[99],i,a,b;
main(x,y,z){
    puts("Please type the key phrase");
    do
        z = getch(),
        i++[d] = (y = clock()) - x, // save amount of time from last key. first value is garbage.
        x = y;
    while((z = getch())-13); // read until carriage return. 
    for(;a < i && getch(); ++a) // don't check for validity, just get a char
        b += abs((y = clock())- x - d[a])*99/d[a], // (y=clock())-x is time from last key.
                                                     // subtract from original time, *99, divide by new
                                                     // then get sum of these
        x = y;
    b < i*9  // check that the average difference is less than 9/99
    || puts("No cake for imposters"); // identify as imposter if greater/equal
    // don't output anything if not an imposter
}

This does not check that the retyped phrase is identical, nor does it output anything if the user is not identified as an imposter.

This also does not consider the time taken after prompting before the first keystroke.

\$\endgroup\$
  • \$\begingroup\$ Shouldn't getch bet getc or getchar? I have an undefined reference to `getch', which if I recall correctly is deprecated? \$\endgroup\$ – coredump Nov 12 '14 at 15:16
  • \$\begingroup\$ I also had "file.c:1:1: warning: data definition has no type or storage class" (gcc). I added char before global declarations, and now, this gives a segmentation fault at runtime. Can you give details about how to build it? What compiler are you using? Thanks. \$\endgroup\$ – coredump Nov 12 '14 at 15:24
  • \$\begingroup\$ @coredump The warnings are harmless; though if you wanted to remove the warnings, it should be type int, and initialized to 0. I've tested this using gcc on Windows (using Window's getch). getch is used instead of getc or getchar because getch doesn't require the return key to be pressed before processing any characters (getch is indeed deprecated on Windows, though there's nothing wrong with using deprecated functions here). \$\endgroup\$ – es1024 Nov 13 '14 at 0:33
  • \$\begingroup\$ I'm testing on Linux and resorted to stackoverflow.com/questions/7469139/… in order to make it works. Thanks. \$\endgroup\$ – coredump Nov 13 '14 at 6:59
1
\$\begingroup\$

Common Lisp : 660

(ql:quickload'(cl-charms alexandria))(defun m(&key(ok 0.2))(labels((^(s)(fresh-line)(princ s)(return-from m))(d(a b)(abs(/ (- b a) b)))($(x)(princ x)(force-output))(?(m)(charms:with-curses()($ m)(clear-input)(charms:enable-raw-input)(loop for c = (read-char)for n = (get-internal-real-time)for x = nil then (/(- n b)internal-time-units-per-second)for b = n when (eql c #\Esc)do (^"QUIT")when x collect x into % until (eql c #\Newline) collect c into ! finally(progn(terpri)(return(cons(coerce !'string)%)))))))(let*((ip(?"INIT PASSWORD: "))(ps(car ip))(sp(if(equal""ps)(^"NO EMPTY PASSWORD ALLOWED")(?"ENTER PASSWORD: ")))(r(if(equal ps(car sp))(alexandria:mean(mapcar #'d(cdr sp)(cdr ip)))(^"YOU DIDN'T SAY THE MAGIC WORD!"))))(if(> r ok)($"YOU ARE A FAKE!")($"IDENTITY CONFIRMED")))))(m)

Ungolfed

(ql:quickload'(cl-charms alexandria))
(defun m(&key(ok 0.2))
  (labels
      ((^(s)(fresh-line)(princ s)(return-from m))
       (d(a b)(abs(/ (- b a) b)))
       ($(x)(princ x)(force-output))
       (?(m)(charms:with-curses()
              (clear-input)
              ($ m)
              (charms:enable-raw-input)
              (loop for c = (read-char)
                    for n = (get-internal-real-time)
                    for x = nil then (/ (- n b)
                                        internal-time-units-per-second)
                    for b = n
                    when (eql c #\Esc)
                      do (^"QUIT")
                    when x
                      collect x into %
                    until (eql c #\Newline)
                    collect c into !
                    finally (progn
                              (terpri)
                              (return
                                (cons (coerce !'string) %)))))))
    (let* ((ip (?"INIT PASSWORD: "))
           (ps (car ip))
           (sp (if (equal "" ps)
                 (^"NO EMPTY PASSWORD ALLOWED")
                 (?"ENTER PASSWORD: ")))
           (r (if (equal ps (car sp))
                (alexandria:mean(mapcar #'d(cdr sp)(cdr ip)))
                (^"YOU DIDN'T SAY THE MAGIC WORD!"))))
      (if (> r ok)
        ($"YOU ARE A FAKE!")
        ($"IDENTITY CONFIRMED")))))

(m) ;; call function

Additional remarks

  • Complies with all rules
  • When user first gives an empty password, the program aborts cleanly
  • When typing Escape, the program aborts cleanly.
  • Tested on recent SBCL and CCL implementations
  • Requires cl-charms, which is a wrapper around Ncurses. This is the easiest way to capture raw input.
  • This is inspired by (but not copied from) the original version found by squeamish-ossifrage

Dinosaur bonus

I should have a bonus because everybody knows that "Common Lisp is a moribund dinosaur".

\$\endgroup\$
  • \$\begingroup\$ Can you switch to code block instead of quote block ? (for your code) \$\endgroup\$ – Optimizer Nov 13 '14 at 9:10
  • \$\begingroup\$ @Optimizer done \$\endgroup\$ – coredump Nov 13 '14 at 9:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.