// -*-Java-*- import java.io.*; import java.net.URL; import java.util.Hashtable; import java.util.HashSet; /** javac Trial.java java Trial http://.... Get the javac and java tools for free from http://java.sun.com. Direct this program to a URL and it will have nine "fingers" (there is only one thumb) type the resulting characters on both a QWERTY and a Dvorak keyboard. Statistics are kept on each finger, and results are given showing how much distance is traveled per finger (in units of key lengths). We can take skewed key layouts (i.e., everything except a Kinesis) into account by setting "int angle = -30," or some similar angle. I still have no idea why they do it, except perhaps for legacy reasons.... If you wish to have this program operate on a file on your own computer, give it an argument along the lines of file:///text.txt, or something along those lines.... IF YOU SHOULD FIND A BUG IN THIS PROGRAM, PLEASE BE SO KIND AS TO SEND ME EMAIL SO I CAN FIX IT AND RUN MY TESTS AGAIN. For all I know, the results that I report are in error, so please help me out in this regard! :) Good luck, +Kleanthes (kgk@koniaris.com). PS: The program should obey all of the rules of correct touch typing. (Famous last words.... :) */ /* A "Trial" is created. The trial creates two instances of "Keyboard". Each "Keyboard" generates a hash table that maps a single character to a "MovePressThunk". The URL is opened, and each character is read. The character is looked up in the Hashtable, and if a result is found (a MovePressThunk), it is invoked. Each thunk asks the finger to press the character; a modifier key (SHIFT, another MovePressThunk) can be invoked if needed. The fingers have a notion of time, so they can return to the home row as needed. Each finger counts the distance that it travels. */ // ====================================================================== public class Trial { // ---------------------------------------------------------------------- // Global variables: // the global time (for all Fingers) static int time; // the time is the keystroke number // ---------------------------------------------------------------------- // define the main method public static void main(String[] args) throws Exception { int angle = -30; // -30 this seems typical of a keyboard? System.out.println("We're running the keyboard with an angle of " + angle); // create a Dvorak and a QWERTY keyboard Keyboard dvorak = new Keyboard(Keyboard.dvorak_shift, Keyboard.dvorak, "Dvorak", angle); Keyboard qwerty = new Keyboard(Keyboard.qwerty_shift, Keyboard.qwerty, "QWERTY", angle); // we should make sure there is exactly one argument. // ??? time = 0; // open the URL URL url = new URL(args[0]); InputStream fr = url.openStream(); // feed every character to each keyboard int c; while ((c = fr.read()) != -1) { // System.out.print(new Character((char) c)); dvorak.type((char) c); qwerty.type((char) c); time++; // advance the time by one unit.... // if ((time % 1000) == 0) System.out.print(c); } // close the file fr.close(); // describe how many characters we read.... System.out.println("We advanced the time to " + time + " characters before seeing the end of stream."); // ask all of the fingers to return to the home row.... dvorak.home(); qwerty.home(); // have the keyboards print their simulation results dvorak.report(); qwerty.report(); } } // ====================================================================== // We don't have thunks in Java, so we can't make a thunk that presses // a key. We need to be able to hold a finger and a location that // needs to be hit---hence the class MovePressThunk. class MovePressThunk { // Local (instance) variables Finger finger; // the finger to move int i; // the local coordinates of the key int j; char c; // my own value int count = 0; // how many times I was pressed.... MovePressThunk shift; // the associated shift key (if needed) // ---------------------------------------------------------------------- // Constructor (make a MovePressThunk) MovePressThunk (char k, Finger f, int x, int y, MovePressThunk s) { c = k; finger = f; i = x; j = y; shift = s; count = 0; } // ---------------------------------------------------------------------- // move the finger and have it press the key.... void movePress () { // move the finger.... finger.movePress(i, j); // if there is a modifier key, hit that as well if (shift != null) { shift.movePress(); } count++; } } // ====================================================================== // a Finger class Finger { // ---------------------------------------------------------------------- // instance variables // name String name; // the name of the finger // the x and y locations in LOCAL COORDINATES (where [0,0] is home) int x, y; // this is how we keep track of Finger distance traveled: double distance; // the number of keystrokes that the Finger hit int strokes; // the time that the last stroke was hit: int last; // skew correction double dyMix; // ---------------------------------------------------------------------- // When we create a Finger, zero everything out.... // Constructor Finger (String n, int angle) { name = n; x = 0; // local position wrt home key y = 0; distance = 0.0; strokes = 0; last = 0; dyMix = Math.sin(Math.toRadians(angle)); } // ---------------------------------------------------------------------- // this move does not return to the home row if enough time has passed.... private void move (int i, int j) { double dx, dy; dy = ((double) (j - y)); dx = ((double) (i - x)) + dy * dyMix; distance += Math.sqrt(dx*dx + dy*dy); x = i; y = j; last = Trial.time; } // ---------------------------------------------------------------------- void home () { move(0,0); } // ---------------------------------------------------------------------- void movePress(int i, int j) { if ((Trial.time - last) > 1) { // go back to the home row home(); } // go to the place that we were asked to move... move(i,j); // don't forget to count the press strokes++; } // ---------------------------------------------------------------------- // this is how we make a Finger report the distance it has moved.... void report() { System.out.println(name + " hit " + strokes + " (along z) and covered " + distance + " key-lengths (in xy-plane)."); } } // ====================================================================== // Should I make a sparse matrix class, or something along those lines // to make a histogram of where the keystrokes are? That would be a // very interesting plot, particularly as a surface. The matrix // should be able to normalize itself to unit area? // ====================================================================== class Keyboard { // Global (class) variables static char[][] dvorak_shift = // 0 0 1 2 3 4 4 5 5 6 7 7 7 (finger, *) {{'~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '[', '+'}, // 2 {0, '"', '<', '>', 'P', 'Y', 'F', 'G', 'C', 'R', 'L', '?'}, // 1 {0, 'A', 'O', 'E', 'U', 'I', 'D', 'H', 'T', 'N', 'S', '_'}, // home {0, ':', 'Q', 'J', 'K', 'X', 'B', 'M', 'W', 'V', 'Z', 0}}; // -1 // make characters like 1=SHIFT, 2=CONTROL, etc.? static char[][] dvorak = {{'`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', ']', '='}, {'\t', '\'', ',', '.', 'p', 'y', 'f', 'g', 'c', 'r', 'l', '/'}, {0, 'a', 'o', 'e', 'u', 'i', 'd', 'h', 't', 'n', 's', '-'}, {1, ';', 'q', 'j', 'k', 'x', 'b', 'm', 'w', 'v', 'z', 1}}; static char[][] qwerty_shift = {{'~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+'}, // 2 {0, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '|'}, {0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"'}, {0, 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0}}; static char[][] qwerty = // the Microsoft natural keyboard {{'`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 0}, {0, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'}, {0, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', 0, 0}, {1, 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 1, 0, 0}}; // ---------------------------------------------------------------------- // local variables String name; // the name of this keyboard MovePressThunk lshift, rshift; Hashtable ht = new Hashtable(200); // lookup a Char and get its MovePressThunk HashSet missing = new HashSet(10); // counts the number of times that a missing character was invoked // we have nine fingers (0 to 7, inclusive, and a thumb, 8): Finger fingers[]; // ---------------------------------------------------------------------- int mapRowToYDisplacement(int r) { // return the height of the finger above the home row return 2 - r; } // ---------------------------------------------------------------------- int mapColumnToFinger(int c) { // see the comment marked (*) above int[] d = {0, 0, 1, 2, 3, 3, 4, 4, 5, 6, 7, 7, 7, 7, 7}; // compare this... return d[c]; } // ---------------------------------------------------------------------- int mapColumnToFingerXDisplacement(int c) { // this is the local displacement from home (0,0) int[] d = {-1, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, 1, 2, 3, 4};// ...to this! return d[c]; } // ---------------------------------------------------------------------- // instantiate a Keyboard // Constructor Keyboard(char[][] shift_map, char[][] map, String n, int angle ) { fingers = new Finger[9]; int i; String names[] = {"lpinky", "lring", "lmiddle", "lindex", "rindex", "rmiddle", "rring", "rpinky", "thumbs"}; // make the fingers for (i=0; i<9; i++) fingers[i] = new Finger(names[i], angle); name = n; // set our name to what we were given // I have to make these in advance because MovePressThunks point to them. // The x and y values are reset when we load the map to their correct values. lshift = new MovePressThunk(' ', null, 0, 0, null); rshift = new MovePressThunk(' ', null, 0, 0, null); // and we add the space key by hand ht.put(new Character(' '), new MovePressThunk(' ', fingers[8], 0, 0, null)); // and this is how we add everything else.... loadMap(map, fingers, false); loadMap(shift_map, fingers, true); } // ---------------------------------------------------------------------- void loadMap(char[][] map, Finger[] fingers, boolean is_shifted) { int rowNum; for (rowNum=0; rowNum 1) { mpt = new MovePressThunk(c, fingers[fingerNum], x, y, shift); ht.put(new Character(c), mpt); } } } } // ---------------------------------------------------------------------- void home() { // ask every finger to go back home int i; for (i=0; i<9; i++) (fingers[i]).home(); } // ---------------------------------------------------------------------- // type a key.... void type(char c) { // find the MovePressThunk and invoke it! MovePressThunk mpt; try { mpt = (MovePressThunk) ht.get(new Character(c)); // we can avoid churning the heap by fixing this.... mpt.movePress(); } catch(Exception e) { // we don't know how to type it.... // we could print the code the first time that we see it??? if (! missing.contains(new Character (c))) { missing.add(new Character(c)); System.out.println(name + ": no handler for: <<" + new Character(c) + ">>"); } } } // ---------------------------------------------------------------------- void report () { int i; Finger f; int total_strokes = 0; double total_distance = 0.0; System.out.println("\n\nReport for " + name + " keyboard."); System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); for (i=0; i<9; i++) { f = fingers[i]; f.report(); total_distance += f.distance; total_strokes += f.strokes; } System.out.println("Total strokes are " + total_strokes + " and total distance is " + total_distance + "."); } } // ======================================================================