Skip to content

Instantly share code, notes, and snippets.

@rowanseymour
Created November 3, 2012 19:40
Show Gist options
  • Save rowanseymour/4008443 to your computer and use it in GitHub Desktop.
Save rowanseymour/4008443 to your computer and use it in GitHub Desktop.
Typor: Typing trainer for people like me
/**
* Typing trainer for people like me whose right hand ain't so amazing
* @author Rowan Seymour
*/
/**
* Test sentence generator
*/
class Generator {
def keys_r = [
['8', 'i', 'k', ',', '9', 'o', 'l', '.'], // Index finger
['0', 'p', ';', '/', '-', '[', '\''], // Middle
['=', ']', '\\'] // Ring finger
]
def keys_r_shifted = [
['*', 'I', 'K', '<', '(', 'O', 'L', '>'], // Index finger
[')', 'P', ':', '?', '_', '{', '"'], // Middle
['+', '}', '|'] // Ring finger
]
def keys_r_flat = keys_r.flatten()
def keys_r_shifted_flat = keys_r_shifted.flatten()
def rand = new Random();
/**
* Generates a test sequence
*/
def generate(int length, Integer finger, boolean withSpaces, boolean withShifts) {
def sb = new StringBuilder()
for (def i = 0; i < length; ++i) {
// Is this going to be a space?
if (withSpaces && i > 0 && i < length - 1 && chance(5))
sb.append(' ')
else if (finger != null) {
def list = withShifts ? (rand.nextBoolean() ? keys_r[finger] : keys_r_shifted[finger]) : keys_r[finger]
sb.append(rand(list))
}
else {
def list = withShifts ? (rand.nextBoolean() ? keys_r_flat : keys_r_shifted_flat) : keys_r_flat
sb.append(rand(list))
}
}
return sb.toString();
}
/**
* Gets a boolean whose chance of being true is 1 in over
*/
def private boolean chance(over) { rand.nextInt(over) == 0 }
/**
* Gets a random character from a list
*/
def private char rand(list) { list[rand.nextInt(list.size)] as char }
}
/**
* Program settings
*/
class Settings {
def filename = System.getProperty("user.home") + "/.typor"
def props = new Properties()
Settings() {
def f = new File(filename)
if (f.exists())
f.withReader { reader -> props.load(reader) }
}
/**
* Saves the settings to file
*/
def save() {
new File(filename).withWriter { writer -> props.store(writer, 'Typor settings') }
}
def Double getBestTime(boolean training, boolean withSpaces, boolean withShifts) {
def key = makeBestTimeKey(training, withSpaces, withShifts)
props[key] as Double
}
def setBestTime(boolean training, boolean withSpaces, boolean withShifts, Double time) {
def key = makeBestTimeKey(training, withSpaces, withShifts)
props[key] = time as String
}
def private String makeBestTimeKey(boolean training, boolean withSpaces, boolean withShifts) {
'besttime_' + (training ? 't' : '') + (withSpaces ? 's' : '') + (withShifts ? 'a' : '')
}
}
// Define the command line interface
def cli = new CliBuilder(usage: 'typor.groovy [options]', footer: 'Type QUIT to quit')
cli.with {
n longOpt: 'num-tests', args: 1, 'number of tests'
t longOpt: 'training-mode', 'per-finger training mode'
s longOpt: 'with-spaces', 'test with spaces'
a longOpt: 'with-shifts', 'test with shifted forms'
h longOpt: 'help', 'print this message'
}
def options = cli.parse(args)
// Print help message if requested
if (options.h) {
cli.usage()
return
}
def generator = new Generator()
def settings = new Settings()
def console = new BufferedReader(new InputStreamReader(System.in))
def numTests = options.n ? options.n as Integer : 10
def training = options.t
def withSpaces = options.s
def withShifts = options.a
def sentenceLength = 20
def times = []
def mistakes = 0
// Main input loop
outer:
while (true) {
def finger = training ? (times.size() % 3) : null;
def test = generator.generate(sentenceLength, finger, withSpaces, withShifts)
def input = ''
def start = System.currentTimeMillis()
while (true) {
println '>> ' + test
print '<< '
input = console.readLine()
if (input.equals('QUIT'))
break outer
else if (test.equals(input))
break
mistakes++
println 'Try again...'
}
times << System.currentTimeMillis() - start
if (numTests != null && times.size() == numTests)
break;
}
def testsCompleted = times.size()
// Print test report
if (testsCompleted > 0) {
def avgTime = ((times.sum() / testsCompleted) / 1000.0)
def currentBest = settings.getBestTime(training, withSpaces, withShifts)
def newRecord = currentBest == null || avgTime < currentBest
println "Completed $testsCompleted test(s)..."
println ' - Training mode: ' + (training ? 'ON' : 'OFF')
println ' - Spaces: ' + (withSpaces ? 'ON' : 'OFF')
println ' - Shifts: ' + (withShifts ? 'ON' : 'OFF')
println "Mistakes: $mistakes"
println "Average time: $avgTime seconds" + (newRecord ? ' (new record!)' : '')
if (newRecord) {
settings.setBestTime(training, withSpaces, withShifts, avgTime)
if (currentBest)
println "Previous best: $currentBest seconds"
}
else if (currentBest)
println "Current best: $currentBest seconds"
}
else
println "Didn't complete any tests"
settings.save()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment