typingtutor 1.0.5 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
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