typingtutor 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d5d378a4ed8de85efdbc0276001fe4edd6f8fd3b
4
- data.tar.gz: 68642a509f9af1459d1da7881a95c36d4bf6b9d7
3
+ metadata.gz: bff0c5f22219fd895592101fcbd84132de5d1886
4
+ data.tar.gz: e3d3fae0c2971d3220866cfddd4d842337a707d7
5
5
  SHA512:
6
- metadata.gz: d3baab8ffbbcdb1ce77db8346c90a51f32820551f087263828fc4b2b5f647ddc13ee643efc0e98554981141eec21f926de3f9b21786473f7cfc328a0965802b8
7
- data.tar.gz: 9a51a1b17155a8bfff441d58a9cf53d8ec85e7943517599295c065ea805bd35509c144719bf6e2b8242bacbbdafdd1099077cef234635d8b167d1187ab14a121
6
+ metadata.gz: 59e91a49e45ed199c9ac7ed9d1096701ed9ffda2d910462f486b8d23fbe0fdeea92c01c43c456bf7dbf62f5d0e5eb236bdabb1d38e73254f19622a4a7909dec1
7
+ data.tar.gz: 5b4b6558abd1da914f24035e103de878ef64f8b5c029f93c41035e1e34da07f237ba78b3a75b992df2ff682c21314dc42e2ace95149418dad50626ca38a99e63
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ * 1.0.6: Added 'improve' exercise and overall stats
4
+ * 1.0.5: Added 'training' exercise based on random dictionary words
5
+ * 1.0.4: Added 'biglebowski' exercise
6
+ * 1.0.3: First version with gem executable working properly
7
+ * 1.0.0/1/2: Test publish gem
data/README.md CHANGED
@@ -20,3 +20,43 @@ $ typingtutor biglebowski
20
20
  ```
21
21
 
22
22
  ![screenshot](https://dl.dropboxusercontent.com/u/28778231/typingtutor/demo.gif)
23
+
24
+ Generic training, generates 250 random english words
25
+
26
+ ```
27
+ $ typingtutor training
28
+ ```
29
+
30
+ Improve on letters you struggle the most:
31
+
32
+ ```
33
+ $ typingtutor improve
34
+ ```
35
+
36
+ See overall stats about your typing:
37
+
38
+ ```
39
+ $ typingtutor stats
40
+ ccuracy per letter:
41
+ 6: 67%
42
+ 7: 75%
43
+ 9: 83%
44
+ m: 89%
45
+ b: 91%
46
+ w: 92%
47
+ j: 92%
48
+ y: 92%
49
+ h: 92%
50
+ n: 92%
51
+ ...
52
+
53
+ Exercises:
54
+ - fox: 7 runs, 56 wpm
55
+ - improve: 3 runs, 36 wpm
56
+
57
+ Exercises played : 10
58
+ Time played: 17m 25s
59
+ Avg speed: 36 wpm
60
+ Max speed: 68 wpm
61
+
62
+ ```
@@ -1,6 +1,9 @@
1
1
  require "highline"
2
2
  require 'io/console'
3
+ require 'yaml'
3
4
  require 'typingtutor/version'
5
+ require 'typingtutor/stats'
4
6
  require 'typingtutor/dictionary'
5
7
  require 'typingtutor/line'
8
+ require 'typingtutor/exercise'
6
9
  require 'typingtutor/runner'
@@ -26,6 +26,7 @@ module Typingtutor
26
26
  end
27
27
 
28
28
  def pick_word_with_letter(letter)
29
+ return nil if @letters[letter].nil?
29
30
  @letters[letter][rand(@letters[letter].size)]
30
31
  end
31
32
 
@@ -36,7 +37,7 @@ module Typingtutor
36
37
  def training_exercise
37
38
  lines = []
38
39
  line = ""
39
- 500.times do
40
+ 250.times do
40
41
  line << (pick_word+" ")
41
42
  if line.size > 70
42
43
  lines << line.strip
@@ -0,0 +1,106 @@
1
+ module Typingtutor
2
+ class Exercise
3
+ attr_accessor :name, :body, :stats, :start, :time
4
+ attr_accessor :chars, :correct_chars, :words, :keystrokes, :typing_accuracy, :word_accuracy, :gross_wpm
5
+
6
+ # class methods
7
+
8
+ def self.load(name:, stats:)
9
+ return nil if name.nil?
10
+ body = load_body(name:name, stats:stats)
11
+ return body.nil? ? nil : Exercise.new(name:name, body:body, stats:stats)
12
+ end
13
+
14
+ def self.load_body(name:, stats:)
15
+ gem_file_name = File.join(File.dirname(__FILE__), '..', '..', "exercises", "#{name}.txt")
16
+
17
+ lines ||= dictionary.training_exercise if name == 'training'
18
+ lines ||= improve_exercise(stats) if name == 'improve'
19
+ # load from exercise folder in the gem
20
+ lines ||= IO.read(name).lines if File.exists?(name)
21
+ lines ||= IO.read("#{name}.txt").lines if File.exists?("#{name}.txt")
22
+ lines ||= IO.read(gem_file_name).lines if File.exists?(gem_file_name)
23
+
24
+ return if lines.nil?
25
+
26
+ lines.each {|line| line.strip!} # remove extra spaces
27
+ lines.reject! {|line| line.strip.to_s == ""} # remove empty lines
28
+ return lines
29
+ end
30
+
31
+ def self.available_exercises
32
+ files = Dir[File.join(File.dirname(__FILE__), '..', '..', "exercises", "*.txt")]
33
+ files.map! do |name|
34
+ name = name.split(File::SEPARATOR).last
35
+ name = name.gsub /\.txt/, ''
36
+ name
37
+ end
38
+ files
39
+ end
40
+
41
+ def self.improve_exercise(stats)
42
+ lines = []
43
+ index = 0
44
+ stats.worst_letters.to_h.each do |letter, accuracy|
45
+ next if dictionary.pick_word_with_letter(letter).nil? # no samples to use
46
+ lines << "You have a #{accuracy}% accuracy on letter #{letter}, let's work on that:"
47
+ 2.times do
48
+ lines << 8.times.map {|i| dictionary.pick_word_with_letter(letter)}.join(" #{letter * (rand(2)+1)} ").strip
49
+ end
50
+ index += 1
51
+ break if index > 5
52
+ end
53
+ lines
54
+ end
55
+
56
+ def self.dictionary
57
+ @dictionary ||= Dictionary.new
58
+ end
59
+
60
+ # instance methods
61
+
62
+ def initialize(name:, body:, stats:)
63
+ self.name = name
64
+ self.body = body
65
+ self.stats = stats
66
+ end
67
+
68
+ def play
69
+ @start = Time.now
70
+ results = body.map { |line| Line.new(line:line, stats:stats).play unless line == ""}
71
+ self.time = Time.now - @start
72
+ self.chars = results.map {|s| s[:chars] }.inject(:+)
73
+ self.correct_chars = results.map {|s| s[:correct_chars] }.inject(:+)
74
+ self.words = results.map {|s| s[:words] }.inject(:+)
75
+ self.keystrokes = results.map {|s| s[:keystrokes] }.inject(:+)
76
+ self.typing_accuracy = (correct_chars.to_f / keystrokes.to_f * 100).to_i
77
+ self.word_accuracy = (correct_chars.to_f / chars.to_f * 100).to_i # TODO
78
+ self.gross_wpm = words / (time / 60)
79
+ stats.record_exercise(self)
80
+ return results
81
+ end
82
+
83
+ def results
84
+ {
85
+ time: time,
86
+ chars: chars,
87
+ correct_chars: correct_chars,
88
+ words: words,
89
+ keystrokes: keystrokes,
90
+ typing_accuracy: typing_accuracy,
91
+ word_accuracy: word_accuracy,
92
+ gross_wpm: gross_wpm
93
+ }
94
+ end
95
+
96
+ def print
97
+ puts
98
+ puts "------------------------"
99
+ puts "Time: #{time.round.divmod(60).join('m ')}s (#{words} words)"
100
+ puts "Speed: #{gross_wpm.round} wpm"
101
+ # puts "Word accuracy: #{word_accuracy}%"
102
+ puts "Typing accuracy: #{typing_accuracy}%"
103
+ end
104
+
105
+ end
106
+ end
@@ -1,10 +1,11 @@
1
1
  module Typingtutor
2
2
  class Line
3
- def initialize(string)
4
- @original = string
3
+ def initialize(line:, stats:)
4
+ @original = line
5
5
  @actual = ""
6
6
  @position = 0
7
7
  @keystrokes = 0
8
+ @stats = stats
8
9
  end
9
10
 
10
11
  def play
@@ -28,7 +29,7 @@ module Typingtutor
28
29
  @keystrokes +=1
29
30
  end
30
31
  end
31
- return stats
32
+ return results
32
33
  end
33
34
 
34
35
  def print_char
@@ -37,6 +38,7 @@ module Typingtutor
37
38
  else
38
39
  print HighLine.color(expected_char, :plain)
39
40
  end
41
+ @stats.record_letter(expected_char, ok?)
40
42
  end
41
43
 
42
44
  def print_backspace
@@ -47,11 +49,11 @@ module Typingtutor
47
49
  def actual_char; @actual[@position]; end
48
50
  def ok?; expected_char == actual_char; end
49
51
 
50
- def stats
51
- { total_chars: @original.length,
52
+ def results
53
+ { chars: @original.length,
52
54
  correct_chars: @original.length.times.select {|i| @original[i] == @actual[i] }.size,
53
55
  keystrokes: @keystrokes,
54
- total_words: @actual.split(' ').size
56
+ words: @actual.split(' ').size
55
57
  }
56
58
  end
57
59
 
@@ -3,13 +3,19 @@ module Typingtutor
3
3
  include HighLine::SystemExtensions
4
4
 
5
5
  def run(exercise_name)
6
- exercise = load_exercise(exercise_name) unless exercise_name.nil?
6
+ if exercise_name == 'stats'
7
+ stats.print_full
8
+ exit(0)
9
+ end
10
+
11
+ exercise = Exercise.load(name:exercise_name, stats:stats)
7
12
  if exercise
8
13
  setup_color_scheme
9
14
  play_intro
10
- @start = Time.now
11
- stats = exercise.map { |line| Line.new(line).play }
12
- print_stats(stats)
15
+ exercise.play
16
+ exercise.print
17
+ stats.print
18
+ stats.save
13
19
  else
14
20
  puts "Typingtutor v#{Typingtutor::VERSION}"
15
21
  puts
@@ -17,8 +23,9 @@ module Typingtutor
17
23
  puts "$ typingtutor path/to/localfile.txt"
18
24
  puts
19
25
  puts "built in exercises:"
20
- puts "- training (dynamically generated)"
21
- exercises.each {|e| puts "- #{e}"}
26
+ puts "- training (250 random english words)"
27
+ puts "- improve (focus on letters you miss the most)"
28
+ Exercise.available_exercises.each {|e| puts "- #{e}"}
22
29
  puts
23
30
  puts "eg: typingtutor biglebowski"
24
31
  end
@@ -41,54 +48,9 @@ module Typingtutor
41
48
  HighLine.color_scheme = cs
42
49
  end
43
50
 
44
- def load_exercise(name)
45
- gem_file_name = File.join(File.dirname(__FILE__), '..', '..', "exercises", "#{name}.txt")
46
-
47
- lines ||= dictionary.training_exercise if name == 'training'
48
- # load from exercise folder in the gem
49
- lines ||= IO.read(name).lines if File.exists?(name)
50
- lines ||= IO.read("#{name}.txt").lines if File.exists?("#{name}.txt")
51
- lines ||= IO.read(gem_file_name).lines if File.exists?(gem_file_name)
52
-
53
- return if lines.nil?
54
-
55
- lines.each {|line| line.strip!} # remove extra spaces
56
- lines.reject! {|line| line.strip.to_s == ""} # remove empty lines
57
- return lines
58
- end
59
-
60
- def exercises
61
- files = Dir[File.join(File.dirname(__FILE__), '..', '..', "exercises", "*.txt")]
62
- files.map! do |name|
63
- name = name.split(File::SEPARATOR).last
64
- name = name.gsub /\.txt/, ''
65
- name
66
- end
67
- files
68
- end
69
-
70
- def dictionary
71
- @dictionary ||= Dictionary.new
51
+ def stats
52
+ @stats ||= Stats.new
72
53
  end
73
54
 
74
- def print_stats(stats)
75
- time = Time.now - @start
76
- total_chars = stats.map {|s| s[:total_chars] }.inject(:+)
77
- correct_chars = stats.map {|s| s[:correct_chars] }.inject(:+)
78
- total_words = stats.map {|s| s[:total_words] }.inject(:+)
79
- keystrokes = stats.map {|s| s[:keystrokes] }.inject(:+)
80
-
81
- typing_accuracy = (correct_chars.to_f / keystrokes.to_f * 100).to_i
82
- word_accuracy = (correct_chars.to_f / total_chars.to_f * 100).to_i # TODO
83
-
84
- gross_wpm = total_words / (time / 60)
85
-
86
- puts
87
- puts "------------------------"
88
- puts "Time: #{time.round(1)}s (#{total_words} words)"
89
- puts "Speed: #{gross_wpm.round} wpm"
90
- puts "Word accuracy: #{word_accuracy}%"
91
- puts "Typing accuracy: #{typing_accuracy}%"
92
- end
93
55
  end # Runner
94
56
  end # Typingtutor
@@ -0,0 +1,88 @@
1
+ module Typingtutor
2
+ class Stats
3
+ FILE = File.expand_path('~/.typingtutor')
4
+
5
+ def initialize
6
+ @stats = {}
7
+ @stats.merge!(YAML.load(IO.read(FILE))) if File.exists?(FILE)
8
+ @stats[:created_at] ||= Time.now
9
+ @stats[:total] ||= {}
10
+ @stats[:exercises] ||= {}
11
+ @stats[:words] ||= {}
12
+ @stats[:letters] ||= {}
13
+ end
14
+
15
+ def save
16
+ @stats[:updated_at] = Time.now
17
+ IO.write(FILE, YAML.dump(@stats))
18
+ end
19
+
20
+ def record_exercise(exercise)
21
+ @stats[:exercises][exercise.name] ||= {}
22
+ @stats[:exercises][exercise.name][:runs] ||= 0
23
+ @stats[:exercises][exercise.name][:runs] += 1
24
+ @stats[:exercises][exercise.name][:last_run] = Time.now
25
+ @stats[:exercises][exercise.name][:last_run_results] = exercise.results
26
+
27
+ # update totals
28
+ @stats[:total][:runs] ||= 0
29
+ @stats[:total][:runs] += 1
30
+
31
+ [:time, :chars, :correct_chars, :words, :keystrokes].each do |metric|
32
+ @stats[:total][metric] ||= 0
33
+ @stats[:total][metric] += exercise.results[metric]
34
+ end
35
+
36
+ @stats[:total][:max_wpm] ||= 0
37
+ @stats[:total][:max_wpm] = [@stats[:total][:max_wpm], exercise.results[:gross_wpm]].max
38
+ @stats[:total][:avg_wpm] = @stats[:total][:words] / (@stats[:total][:time] / 60)
39
+ end
40
+
41
+ def record_word(word)
42
+ # TODO
43
+ end
44
+
45
+ def record_letter(letter, ok)
46
+ return if letter == ' '
47
+ @stats[:letters][letter] ||= {}
48
+ @stats[:letters][letter][:total] ||= {}
49
+ @stats[:letters][letter][:total][:count] ||= 0
50
+ @stats[:letters][letter][:total][:count] += 1
51
+ @stats[:letters][letter][:total][:correct] ||= 0
52
+ @stats[:letters][letter][:total][:correct] += 1 if ok
53
+ @stats[:letters][letter][:total][:accuracy] = (@stats[:letters][letter][:total][:correct].to_f / @stats[:letters][letter][:total][:count].to_f * 100).round
54
+ end
55
+
56
+ def print
57
+ puts "------------------------"
58
+ puts "Your avg speed: #{@stats[:total][:avg_wpm].round} wpm"
59
+ puts "Your max speed: #{@stats[:total][:max_wpm].round} wpm"
60
+ end
61
+
62
+ def print_full
63
+ puts "Accuracy per letter:"
64
+ worst_letters.each {|letter, accuracy| puts "#{letter}: #{accuracy.round}%"}
65
+
66
+ if @stats[:exercises].size > 0
67
+ puts
68
+ puts "Exercises:"
69
+ @stats[:exercises].each do |name, data|
70
+ puts "- #{name}: #{data[:runs]} runs, #{data[:last_run_results][:gross_wpm].round} wpm"
71
+ end
72
+ end
73
+ puts
74
+ puts "Exercises played: #{@stats[:total][:runs] || 0}"
75
+ puts "Time played: #{(@stats[:total][:time] || 0).round.divmod(60).join('m ')}s"
76
+ puts "Avg speed: #{(@stats[:total][:avg_wpm] || 0).round} wpm"
77
+ puts "Max speed: #{(@stats[:total][:max_wpm] || 0).round} wpm"
78
+ end
79
+
80
+ def worst_letters
81
+ accuracy = @stats[:letters].map {|letter, data| [letter, data[:total][:accuracy]]}.to_h # { "a" => 100, "b" => 98 }
82
+ accuracy.reject! {|key, value| value == 100}
83
+ accuracy.reject! {|key, value| key !~ /\w/ }
84
+ accuracy = accuracy.sort_by {|key, value| value }.to_h
85
+ accuracy
86
+ end
87
+ end
88
+ end
@@ -1,3 +1,3 @@
1
1
  module Typingtutor
2
- VERSION = "1.0.5"
2
+ VERSION = "1.0.6"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: typingtutor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paolo Dona
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-03 00:00:00.000000000 Z
11
+ date: 2016-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -48,6 +48,7 @@ extra_rdoc_files: []
48
48
  files:
49
49
  - ".DS_Store"
50
50
  - ".gitignore"
51
+ - CHANGELOG.md
51
52
  - Gemfile
52
53
  - Gemfile.lock
53
54
  - LICENSE
@@ -210,8 +211,10 @@ files:
210
211
  - exercises/starwars.txt
211
212
  - lib/typingtutor.rb
212
213
  - lib/typingtutor/dictionary.rb
214
+ - lib/typingtutor/exercise.rb
213
215
  - lib/typingtutor/line.rb
214
216
  - lib/typingtutor/runner.rb
217
+ - lib/typingtutor/stats.rb
215
218
  - lib/typingtutor/version.rb
216
219
  - typingtutor.gemspec
217
220
  homepage: https://github.com/paolodona/typingtutor