Various improvements.

- Added -showwpm
 - Added -g
 - Added -noreport
 - Updated generated themes.
 - Improved automatic line wrapping.
 - Fixed timer bug.
 - Moved -o to -oneshot
 - The exit code now corresponds to the exit action
 - Updated documentation.
This commit is contained in:
Aetnaeus
2020-12-27 20:59:20 -05:00
parent a3e308cab6
commit 4eae6a11f4
10 changed files with 5357 additions and 1913 deletions

@ -2,6 +2,8 @@ all:
go build -o bin/tt *.go
install:
install -m755 bin/tt /usr/local/bin
assets:
python3 tools/themegen.py | gofmt > generatedThemes.go
rel:
GOOS=darwin GOARCH=386 go build -o binaries/tt-osx_386 *.go
GOOS=darwin GOARCH=amd64 go build -o binaries/tt-osx_amd64 *.go

@ -33,31 +33,17 @@ Best served on a terminal with truecolor and cursor shape support (e.g kitty, it
# Usage
By default 50 words from the top 200 words in the English language are used to
By default 50 words from the top 1000 words in the English language are used to
constitute the test. Custom text can be supplied by piping aribirary text to
the program. Each paragraph in the input is shown as a separate segment of the
text.
E.G
- `shuf -n 40 /etc/dictionaries-common/words|tt` produces a test consisting of 40 random words drawn from `/etc/dictionaries-common/words`.
- `curl http://api.quotable.io/random|jq -r .content|tt` produces a test consisting of a random quote.
- `tt -n <num>` produces a test consisting of *num* randomly drawn english words
- `tt -t 10` starts a test which timesout after 10 seconds.
- `tt -theme <theme>` Starts tt with the provided theme (see `-list themes` for a list of options)
- `tt -csv` outputs the csv formatted results to STDOUT
The default behaviour is equivalent to 'tt -n 50'
See `-help` for an exhaustive and up to date list.
## Keys
- Pressing `escape` at any point restarts the test.
- `C-c` exits the test.
# Configuration
## Configuration
The theme can be configured by setting the following options in `~/.ttrc`:
@ -68,3 +54,26 @@ The theme can be configured by setting the following options in `~/.ttrc`:
- `hicol3`: The colour used to highlight the next word.
- `errcol`: The colour used to highlight errors.
- `theme`: The theme from which default colors are drawn, a list of builtin themes can be obtained via `-list themes`.
## Examples
- `tt -n 10` produces a test consisting of 10 randomly drawn english words
- `tt -n 50 -g 5` produces a test consisting of 50 randomly drawn words in 5 groups of 10 words each.
- `tt -t 10` starts a timed test consisting of 50 words
- `tt -theme gruvbox` Starts tt with the gruvbox theme
The default behaviour is equivalent to `tt -n 50`.
See `-help` for an exhaustive list of options.
## Recipes
`tt` is designed to be easily scriptable and integrate nicely with other with
other *nix tools. With a little shell scripting most features the user can
conceive of should be possible to implement. Below are some simple examples of
what can be achieved.
- `shuf -n 40 /usr/local/dict/words|tt` Produces a test consisting of 40 random words drawn from your system's dictionary.
- `curl http://api.quotable.io/random|jq -r .content|tt` Produces a test consisting of a random quote.
- `alias ttd='tt -csv >> ~/wpm.csv'` Creates an alias called ttd which keeps a log of your progress in your home directory`.

1438
generatedThemes.go Normal file

File diff suppressed because it is too large Load Diff

1635
themes.go

File diff suppressed because it is too large Load Diff

3597
tools/terminal_themes.json Normal file

File diff suppressed because it is too large Load Diff

48
tools/themegen.py Normal file

@ -0,0 +1,48 @@
import os
import json
# Generates themes from terminal_themes.json (itself generated from some long forgotten github repo [possibly guake]).
f = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'terminal_themes.json')
themes = json.loads(open(f).read())
def blend(src, dest, opacity):
src = src[1:]
dest = dest[1:]
dr, dg, db = [int(dest[i:i + 2], 16) for i in (0, 2, 4)]
sr, sg, sb = [int(src[i:i + 2], 16) for i in (0, 2, 4)]
sr = sr * opacity + dr * (1 - opacity)
sg = sg * opacity + dg * (1 - opacity)
sb = sb * opacity + db * (1 - opacity)
return "#%.2x%.2x%.2x" % (int(sr), int(sg), int(sb))
print("//GENERATED CODE, DO NOT EDIT BY HAND (see themegen.py)\n\n")
print("package main\n")
print("var generatedThemes = map[string]map[string]string{")
for name, t in themes.items():
print('\t"%s": map[string]string{' % name)
# Meat (alter these to taste)
mapping = {
"bgcol": t['background'],
"fgcol": t['foreground'],
"hicol": t['color7'],
"hicol2": blend(t['background'], t['color9'], .3),
"hicol3": t['color9'],
"errcol": t['color1'],
}
for k, v in mapping.items():
print('\t\t"%s": "%s",' % (k, v))
print("\t},")
print("}")

110
tt.go

@ -17,12 +17,12 @@ import (
var scr tcell.Screen
var csvMode bool
var rawMode bool
type result struct {
wpm int
cpm int
accuracy float64
wpm int
cpm int
accuracy float64
timestamp int64
}
var results []result
@ -45,16 +45,16 @@ func readConfig() map[string]string {
return cfg
}
func exit() {
func exit(rc int) {
scr.Fini()
if csvMode {
for _, r := range results {
fmt.Printf("%d,%d,%.2f\n", r.wpm, r.cpm, r.accuracy)
fmt.Printf("%d,%d,%.2f,%d\n", r.wpm, r.cpm, r.accuracy, r.timestamp)
}
}
os.Exit(0)
os.Exit(rc)
}
func showReport(scr tcell.Screen, cpm, wpm int, accuracy float64) {
@ -69,30 +69,37 @@ func showReport(scr tcell.Screen, cpm, wpm int, accuracy float64) {
if key, ok := scr.PollEvent().(*tcell.EventKey); ok && key.Key() == tcell.KeyEscape {
return
} else if ok && key.Key() == tcell.KeyCtrlC {
exit()
exit(1)
}
}
}
func main() {
var n int
var contentFn func() []string
var ngroups int
var contentFn func(sw, sh int) []string
var rawMode bool
var oneShotMode bool
var wrapSz int
var maxLineLen int
var noSkip bool
var noReport bool
var timeout int
var listFlag string
var err error
var themeName string
var showWpm bool
flag.IntVar(&n, "n", 50, "The number of random words which constitute the test.")
flag.IntVar(&wrapSz, "w", 80, "Wraps the input text at the given number of columns (ignored if -raw is present).")
flag.IntVar(&ngroups, "g", 1, "The number of groups into which the generated test is split.")
flag.IntVar(&maxLineLen, "w", 80, "The maximum line length in characters. (ignored if -raw is present).")
flag.IntVar(&timeout, "t", -1, "Terminate the test after the given number of seconds.")
flag.BoolVar(&showWpm, "showwpm", false, "Display WPM whilst typing.")
flag.BoolVar(&noSkip, "noskip", false, "Disable word skipping when space is pressed.")
flag.BoolVar(&csvMode, "csv", false, "Print the test results to stdout in the form <wpm>,<cpm>,<accuracy>.")
flag.BoolVar(&oneShotMode, "oneshot", false, "Automatically exit after a single run (useful for scripts).")
flag.BoolVar(&noReport, "noreport", false, "Don't show a report at the end of the test (useful in conjunction with -o).")
flag.BoolVar(&csvMode, "csv", false, "Print the test results to stdout in the form wpm,cpm,accuracy,time.")
flag.BoolVar(&rawMode, "raw", false, "Don't reflow text or show one paragraph at a time.")
flag.BoolVar(&oneShotMode, "o", false, "Automatically exit after a single run (useful for scripts).")
flag.StringVar(&themeName, "theme", "", "The theme to use (overrides ~/.ttrc).")
flag.StringVar(&listFlag, "list", "", "-list themes prints a list of available themes.")
@ -134,20 +141,46 @@ Options:`)
}
if rawMode {
contentFn = func() []string { return []string{string(b)} }
contentFn = func(sw, sh int) []string { return []string{string(b)} }
} else {
s := strings.Replace(string(b), "\r", "", -1)
s = regexp.MustCompile("\n\n+").ReplaceAllString(s, "\n\n")
content := strings.Split(strings.Trim(s, "\n"), "\n\n")
contentFn = func(sw, sh int) []string {
wsz := maxLineLen
if wsz > sw {
wsz = sw - 8
}
for i, _ := range content {
content[i] = strings.Replace(wordWrap(strings.Trim(content[i], " "), wrapSz), "\n", " \n", -1)
s := strings.Replace(string(b), "\r", "", -1)
s = regexp.MustCompile("\n\n+").ReplaceAllString(s, "\n\n")
content := strings.Split(strings.Trim(s, "\n"), "\n\n")
for i, _ := range content {
content[i] = strings.Replace(wordWrap(strings.Trim(content[i], " "), wsz), "\n", " \n", -1)
}
return content
}
contentFn = func() []string { return content }
}
} else {
contentFn = func() []string { return []string{randomText(n, wrapSz)} }
contentFn = func(sw, sh int) []string {
wsz := maxLineLen
if wsz > sw {
wsz = sw - 8
}
if ngroups > n {
ngroups = n
}
r := make([]string, ngroups)
sz := n / ngroups
for i := 0; i < ngroups-1; i++ {
r[i] = randomText(sz, wsz)
}
r[ngroups-1] = randomText(sz+n%ngroups, wsz)
return r
}
}
cfg := readConfig()
@ -214,29 +247,38 @@ Options:`)
}
typer := NewTyper(scr, fgcol, bgcol, hicol, hicol2, hicol3, errcol)
if noSkip {
typer.SkipWord = false
}
typer.SkipWord = !noSkip
typer.ShowWpm = showWpm
if timeout != -1 {
timeout *= 1E9
}
for {
nerrs, ncorrect, t, exitKey := typer.Start(contentFn(), time.Duration(timeout))
sw, sh := scr.Size()
nerrs, ncorrect, t, rc := typer.Start(contentFn(sw, sh), time.Duration(timeout))
switch exitKey {
case 0:
switch rc {
case TyperComplete:
cpm := int(float64(ncorrect) / (float64(t) / 60E9))
wpm := cpm / 5
accuracy := float64(ncorrect) / float64(nerrs+ncorrect) * 100
results = append(results, result{wpm, cpm, accuracy})
if oneShotMode {
exit()
results = append(results, result{wpm, cpm, accuracy, time.Now().Unix()})
if !noReport {
showReport(scr, cpm, wpm, accuracy)
}
showReport(scr, cpm, wpm, accuracy)
case tcell.KeyCtrlC:
exit()
if oneShotMode {
exit(0)
}
case TyperSigInt:
exit(1)
case TyperResize:
//Resize events restart the test, this shouldn't be a problem in the vast majority of cases
//and allows us to avoid baking rewrapping logic into the typer.
//TODO: implement state-preserving resize (maybe)
}
}
}

@ -2,16 +2,26 @@ package main
import (
"fmt"
"os"
"strconv"
"time"
"github.com/gdamore/tcell"
)
const (
TyperComplete = iota
TyperSigInt
TyperEscape
TyperResize
)
type typer struct {
Scr tcell.Screen
OnStart func()
SkipWord bool
ShowWpm bool
tty *os.File
currentWordStyle tcell.Style
nextWordStyle tcell.Style
@ -26,9 +36,14 @@ func NewTyper(scr tcell.Screen, fgcol, bgcol, hicol, hicol2, hicol3, errcol tcel
Foreground(fgcol).
Background(bgcol)
tty, err := os.OpenFile("/dev/tty", os.O_WRONLY, 0)
if err != nil {
panic(err)
}
return &typer{
Scr: scr,
SkipWord: true,
tty: tty,
backgroundStyle: def,
correctStyle: def.Foreground(hicol),
@ -52,7 +67,7 @@ func (t *typer) highlight(text []cell, idx int, currentWordStyle, nextWordStyle
}
}
func (t *typer) Start(text []string, timeout time.Duration) (nerrs, ncorrect int, duration time.Duration, exitKey tcell.Key) {
func (t *typer) Start(text []string, timeout time.Duration) (nerrs, ncorrect int, duration time.Duration, rc int) {
timeLeft := timeout
for i, p := range text {
@ -64,7 +79,7 @@ func (t *typer) Start(text []string, timeout time.Duration) (nerrs, ncorrect int
startImmediately = false
}
e, c, exitKey, d = t.start(p, timeLeft, startImmediately)
e, c, rc, d = t.start(p, timeLeft, startImmediately)
nerrs += e
ncorrect += c
@ -77,7 +92,7 @@ func (t *typer) Start(text []string, timeout time.Duration) (nerrs, ncorrect int
}
}
if exitKey != 0 {
if rc != TyperComplete {
return
}
}
@ -85,13 +100,12 @@ func (t *typer) Start(text []string, timeout time.Duration) (nerrs, ncorrect int
return
}
func (t *typer) start(s string, timeLimit time.Duration, startImmediately bool) (nerrs int, ncorrect int, exitKey tcell.Key, duration time.Duration) {
func (t *typer) start(s string, timeLimit time.Duration, startImmediately bool) (nerrs int, ncorrect int, rc int, duration time.Duration) {
var startTime time.Time
text := stringToCells(s)
nc, nr := calcStringDimensions(s)
sw, sh := scr.Size()
nc, nr := calcStringDimensions(s)
x := (sw - nc) / 2
y := (sh - nr) / 2
@ -99,27 +113,15 @@ func (t *typer) start(s string, timeLimit time.Duration, startImmediately bool)
text[i].style = t.backgroundStyle
}
fmt.Printf("\033[5 q")
t.tty.WriteString("\033[5 q")
//Assumes original cursor shape was a block (the one true cursor shape), there doesn't appear to be a
//good way to save/restore the shape if the user has changed it from the otcs.
defer fmt.Printf("\033[2 q")
defer t.tty.WriteString("\033[2 q")
t.Scr.SetStyle(t.backgroundStyle)
idx := 0
redraw := func() {
if timeLimit != -1 && !startTime.IsZero() {
remaining := timeLimit - time.Now().Sub(startTime)
drawString(t.Scr, x+nc/2, y+nr+1, strconv.Itoa(int(remaining/1E9)), -1, t.backgroundStyle)
}
//Potentially inefficient, but seems to be good enough
drawCells(t.Scr, x, y, text, idx)
t.Scr.Show()
}
calcStats := func() {
nerrs = 0
ncorrect = 0
@ -132,10 +134,32 @@ func (t *typer) start(s string, timeLimit time.Duration, startImmediately bool)
}
}
exitKey = 0
rc = TyperComplete
duration = time.Now().Sub(startTime)
}
redraw := func() {
if timeLimit != -1 && !startTime.IsZero() {
remaining := timeLimit - time.Now().Sub(startTime)
drawString(t.Scr, x+nc/2, y+nr+1, " ", -1, t.backgroundStyle)
drawString(t.Scr, x+nc/2, y+nr+1, strconv.Itoa(int(remaining/1E9)), -1, t.backgroundStyle)
}
if t.ShowWpm && !startTime.IsZero() {
calcStats()
if duration > 1E7 { //Avoid flashing large numbers on test start.
wpm := int((float64(ncorrect) / 5) / (float64(duration) / 60E9))
drawString(t.Scr, x+nc/2-4, y-2, fmt.Sprintf("WPM: %-10d\n", wpm), -1, t.backgroundStyle)
}
}
//Potentially inefficient, but seems to be good enough
drawCells(t.Scr, x, y, text, idx)
t.Scr.Show()
}
deleteWord := func() {
t.highlight(text, idx, t.backgroundStyle, t.backgroundStyle)
@ -173,7 +197,7 @@ func (t *typer) start(s string, timeLimit time.Duration, startImmediately bool)
default:
}
time.Sleep(time.Duration(1E8))
time.Sleep(time.Duration(5E8))
t.Scr.PostEventWait(nil)
}
}
@ -194,25 +218,20 @@ func (t *typer) start(s string, timeLimit time.Duration, startImmediately bool)
switch ev := ev.(type) {
case *tcell.EventResize:
t.Scr.Sync()
t.Scr.Clear()
nc, nr = calcStringDimensions(s)
sw, sh = scr.Size()
x = (sw - nc) / 2
y = (sh - nr) / 2
rc = TyperResize
return
case *tcell.EventKey:
if startTime.IsZero() {
startTime = time.Now()
}
switch key := ev.Key(); key {
case tcell.KeyEscape,
tcell.KeyCtrlC:
case tcell.KeyCtrlC:
rc = TyperSigInt
nerrs = -1
ncorrect = -1
exitKey = key
return
case tcell.KeyEscape:
rc = TyperEscape
return
case tcell.KeyCtrlL:
@ -288,5 +307,6 @@ func (t *typer) start(s string, timeLimit time.Duration, startImmediately bool)
redraw()
}
}
}

205
util.go

@ -53,211 +53,8 @@ func init() {
func randomText(n int, ncols int) string {
r := ""
words := []string{
"the",
"be",
"of",
"and",
"a",
"to",
"in",
"he",
"have",
"it",
"that",
"for",
"they",
"I",
"with",
"as",
"not",
"on",
"she",
"at",
"by",
"this",
"we",
"you",
"do",
"but",
"from",
"or",
"which",
"one",
"would",
"all",
"will",
"there",
"say",
"who",
"make",
"when",
"can",
"more",
"if",
"no",
"man",
"out",
"other",
"so",
"what",
"time",
"up",
"go",
"about",
"than",
"into",
"could",
"state",
"only",
"new",
"year",
"some",
"take",
"come",
"these",
"know",
"see",
"use",
"get",
"like",
"then",
"first",
"any",
"work",
"now",
"may",
"such",
"give",
"over",
"think",
"most",
"even",
"find",
"day",
"also",
"after",
"way",
"many",
"must",
"look",
"before",
"great",
"back",
"through",
"long",
"where",
"much",
"should",
"well",
"people",
"down",
"own",
"just",
"because",
"good",
"each",
"those",
"feel",
"seem",
"how",
"high",
"too",
"place",
"little",
"world",
"very",
"still",
"nation",
"hand",
"old",
"life",
"tell",
"write",
"become",
"here",
"show",
"house",
"both",
"between",
"need",
"mean",
"call",
"develop",
"under",
"last",
"right",
"move",
"thing",
"general",
"school",
"never",
"same",
"another",
"begin",
"while",
"number",
"part",
"turn",
"real",
"leave",
"might",
"want",
"point",
"form",
"off",
"child",
"few",
"small",
"since",
"against",
"ask",
"late",
"home",
"interest",
"large",
"person",
"end",
"open",
"public",
"follow",
"during",
"present",
"without",
"again",
"hold",
"govern",
"around",
"possible",
"head",
"consider",
"word",
"program",
"problem",
"however",
"lead",
"system",
"set",
"order",
"eye",
"plan",
"run",
"keep",
"face",
"fact",
"group",
"play",
"stand",
"increase",
"early",
"course",
"change",
"help",
"line",
}
for i := 0; i < n; i++ {
r += words[rand.Int()%len(words)]
r += defaultWordList[rand.Int()%len(defaultWordList)]
if i != n-1 {
r += " "
}

106
words.go Normal file

@ -0,0 +1,106 @@
package main
//Top 1000 words in English.
var defaultWordList = []string{
"as", "I", "his", "that", "he", "was", "for", "on", "are", "with",
"they", "be", "at", "one", "have", "this", "from", "by", "hot", "word",
"but", "what", "some", "is", "it", "you", "or", "had", "the", "of",
"to", "and", "a", "in", "we", "can", "out", "other", "were", "which",
"do", "their", "time", "if", "will", "how", "said", "an", "each", "tell",
"does", "set", "three", "want", "air", "well", "also", "play", "small", "end",
"put", "home", "read", "hand", "port", "large", "spell", "add", "even", "land",
"here", "must", "big", "high", "such", "follow", "act", "why", "ask", "men",
"change", "went", "light", "kind", "off", "need", "house", "picture", "try", "us",
"again", "animal", "point", "mother", "world", "near", "build", "self", "earth", "father",
"any", "new", "work", "part", "take", "get", "place", "made", "live", "where",
"after", "back", "little", "only", "round", "man", "year", "came", "show", "every",
"good", "me", "give", "our", "under", "name", "very", "through", "just", "form",
"sentence", "great", "think", "say", "help", "low", "line", "differ", "turn", "cause",
"much", "mean", "before", "move", "right", "boy", "old", "too", "same", "she",
"all", "there", "when", "up", "use", "your", "way", "about", "many", "then",
"them", "write", "would", "like", "so", "these", "her", "long", "make", "thing",
"see", "him", "two", "has", "look", "more", "day", "could", "go", "come",
"did", "number", "sound", "no", "most", "people", "my", "over", "know", "water",
"than", "call", "first", "who", "may", "down", "side", "been", "now", "find",
"head", "stand", "own", "page", "should", "country", "found", "answer", "school", "grow",
"study", "still", "learn", "plant", "cover", "food", "sun", "four", "between", "state",
"keep", "eye", "never", "last", "let", "thought", "city", "tree", "cross", "farm",
"hard", "start", "might", "story", "saw", "far", "sea", "draw", "left", "late",
"run", "dont", "while", "press", "close", "night", "real", "life", "few", "north",
"book", "carry", "took", "science", "eat", "room", "friend", "began", "idea", "fish",
"mountain", "stop", "once", "base", "hear", "horse", "cut", "sure", "watch", "color",
"face", "wood", "main", "open", "seem", "together", "next", "white", "children", "begin",
"got", "walk", "example", "ease", "paper", "group", "always", "music", "those", "both",
"mark", "often", "letter", "until", "mile", "river", "car", "feet", "care", "second",
"enough", "plain", "girl", "usual", "young", "ready", "above", "ever", "red", "list",
"though", "feel", "talk", "bird", "soon", "body", "dog", "family", "direct", "pose",
"leave", "song", "measure", "door", "product", "black", "short", "numeral", "class", "wind",
"question", "happen", "complete", "ship", "area", "half", "rock", "order", "fire", "south",
"problem", "piece", "told", "knew", "pass", "since", "top", "whole", "king", "street",
"inch", "multiply", "nothing", "course", "stay", "wheel", "full", "force", "blue", "object",
"decide", "surface", "deep", "moon", "island", "foot", "system", "busy", "test", "record",
"boat", "common", "gold", "possible", "plane", "stead", "dry", "wonder", "laugh", "thousand",
"ago", "ran", "check", "game", "shape", "equate", "hot", "miss", "brought", "heat",
"snow", "tire", "bring", "yes", "distant", "fill", "east", "paint", "language", "among",
"unit", "power", "town", "fine", "certain", "fly", "fall", "lead", "cry", "dark",
"machine", "note", "wait", "plan", "figure", "star", "box", "noun", "field", "rest",
"correct", "able", "pound", "done", "beauty", "drive", "stood", "contain", "front", "teach",
"week", "final", "gave", "green", "oh", "quick", "develop", "ocean", "warm", "free",
"minute", "strong", "special", "mind", "behind", "clear", "tail", "produce", "fact", "space",
"heard", "best", "hour", "better", "true", "during", "hundred", "five", "remember", "step",
"early", "hold", "west", "ground", "interest", "reach", "fast", "verb", "sing", "listen",
"six", "table", "travel", "less", "morning", "ten", "simple", "several", "vowel", "toward",
"war", "lay", "against", "pattern", "slow", "center", "love", "person", "money", "serve",
"appear", "road", "map", "rain", "rule", "govern", "pull", "cold", "notice", "voice",
"energy", "hunt", "probable", "bed", "brother", "egg", "ride", "cell", "believe", "perhaps",
"pick", "sudden", "count", "square", "reason", "length", "represent", "art", "subject", "region",
"size", "vary", "settle", "speak", "weight", "general", "ice", "matter", "circle", "pair",
"include", "divide", "syllable", "felt", "grand", "ball", "yet", "wave", "drop", "heart",
"am", "present", "heavy", "dance", "engine", "position", "arm", "wide", "sail", "material",
"fraction", "forest", "sit", "race", "window", "store", "summer", "train", "sleep", "prove",
"lone", "leg", "exercise", "wall", "catch", "mount", "wish", "sky", "board", "joy",
"winter", "sat", "written", "wild", "instrument", "kept", "glass", "grass", "cow", "job",
"edge", "sign", "visit", "past", "soft", "fun", "bright", "gas", "weather", "month",
"million", "bear", "finish", "happy", "hope", "flower", "clothe", "strange", "gone", "trade",
"melody", "trip", "office", "receive", "row", "mouth", "exact", "symbol", "die", "least",
"trouble", "shout", "except", "wrote", "seed", "tone", "join", "suggest", "clean", "break",
"lady", "yard", "rise", "bad", "blow", "oil", "blood", "touch", "grew", "cent",
"mix", "team", "wire", "cost", "lost", "brown", "wear", "garden", "equal", "sent",
"choose", "fell", "fit", "flow", "fair", "bank", "collect", "save", "control", "decimal",
"ear", "else", "quite", "broke", "case", "middle", "kill", "son", "lake", "moment",
"scale", "loud", "spring", "observe", "child", "straight", "consonant", "nation", "dictionary", "milk",
"speed", "method", "organ", "pay", "age", "section", "dress", "cloud", "surprise", "quiet",
"stone", "tiny", "climb", "cool", "design", "poor", "lot", "experiment", "bottom", "key",
"iron", "single", "stick", "flat", "twenty", "skin", "smile", "crease", "hole", "jump",
"baby", "eight", "village", "meet", "root", "buy", "raise", "solve", "metal", "whether",
"push", "seven", "paragraph", "third", "shall", "held", "hair", "describe", "cook", "floor",
"either", "result", "burn", "hill", "safe", "cat", "century", "consider", "type", "law",
"bit", "coast", "copy", "phrase", "silent", "tall", "sand", "soil", "roll", "temperature",
"finger", "industry", "value", "fight", "lie", "beat", "excite", "natural", "view", "sense",
"capital", "wont", "chair", "danger", "fruit", "rich", "thick", "soldier", "process", "operate",
"practice", "separate", "difficult", "doctor", "please", "protect", "noon", "crop", "modern", "element",
"hit", "student", "corner", "party", "supply", "whose", "locate", "ring", "character", "insect",
"caught", "period", "indicate", "radio", "spoke", "atom", "human", "history", "effect", "electric",
"expect", "bone", "rail", "imagine", "provide", "agree", "thus", "gentle", "woman", "captain",
"guess", "necessary", "sharp", "wing", "create", "neighbor", "wash", "bat", "rather", "crowd",
"corn", "compare", "poem", "string", "bell", "depend", "meat", "rub", "tube", "famous",
"dollar", "stream", "fear", "sight", "thin", "triangle", "planet", "hurry", "chief", "colony",
"clock", "mine", "tie", "enter", "major", "fresh", "search", "send", "yellow", "gun",
"allow", "print", "dead", "spot", "desert", "suit", "current", "lift", "rose", "arrive",
"master", "track", "parent", "shore", "division", "sheet", "substance", "favor", "connect", "post",
"spend", "chord", "fat", "glad", "original", "share", "station", "dad", "bread", "charge",
"proper", "bar", "offer", "segment", "slave", "duck", "instant", "market", "degree", "populate",
"chick", "dear", "enemy", "reply", "drink", "occur", "support", "speech", "nature", "range",
"steam", "motion", "path", "liquid", "log", "meant", "quotient", "teeth", "shell", "neck",
"oxygen", "sugar", "death", "pretty", "skill", "women", "season", "solution", "magnet", "silver",
"thank", "branch", "match", "suffix", "especially", "fig", "afraid", "huge", "sister", "steel",
"discuss", "forward", "similar", "guide", "experience", "score", "apple", "bought", "led", "pitch",
"coat", "mass", "card", "band", "rope", "slip", "win", "dream", "evening", "condition",
"feed", "tool", "total", "basic", "smell", "valley", "nor", "double", "seat", "continue",
"block", "chart", "hat", "sell", "success", "company", "subtract", "event", "particular", "deal",
"swim", "term", "opposite", "wife", "shoe", "shoulder", "spread", "arrange", "camp", "invent",
"cotton", "born", "determine", "quart", "nine", "truck", "noise", "level", "chance", "gather",
"shop", "stretch", "throw", "shine", "property", "column", "molecule", "select", "wrong", "gray",
"repeat", "require", "broad", "prepare", "salt", "nose", "plural", "anger", "claim", "continent",
}