typing_trainer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5b3afbdba090c7a2a268af3a7329fa7e030819b33a2b0a201b65b62b7f453475
4
+ data.tar.gz: 0425d685fda5e33b89618aebc0f8ca2c52cc99e3f6e394520d7663cc91420240
5
+ SHA512:
6
+ metadata.gz: 6ab033cdd3268b4ba6c51932e4c76a35a989e0313e0c0cc4488d18259a7549e86db6693299a302bf30a9459a749e7ee1c70f63bc8b4a5e1428fb091580d7dd9f
7
+ data.tar.gz: cd32d9e32af941911bc3d67a1404e1dfea2be0c198bce04347c7a81e6c9a4d646bddee8c7cd152d238c629542c53da2de3e24e99a31bd94ff07cce1af2d2455f
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1 @@
1
+ 2.6.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in typing_trainer.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Juan Germán Castañeda Echevarría
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,49 @@
1
+ # TypingTrainer
2
+
3
+ Learn to touch type from your command line!
4
+
5
+ ## Gameplay screenshots
6
+
7
+ ![Screenshot](screenshots/playing.png)
8
+
9
+
10
+ ## Installation
11
+
12
+ $ gem install 'typing_trainer'
13
+
14
+ ## Starting the game
15
+
16
+ $ typing_trainer
17
+
18
+ To exit during game, use `Ctrl-C` or wait until asked if you want to continue playing.
19
+
20
+ ## Usage
21
+
22
+ $ typing_trainer --help
23
+
24
+ Usage: typing_trainer [options]
25
+ -l, --level LEVEL The starting level to play
26
+ -f, --file FILEPATH Path to file containing custom text to use
27
+ -a, --advanced Hide finger help (default show)
28
+ -d, --debug Show debugging messages (default hide)
29
+
30
+ ## Contributing
31
+
32
+ 1. Fork it
33
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
34
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
35
+ 4. Push to the branch (`git push origin my-new-feature`)
36
+ 5. Create new Pull Request
37
+
38
+ ## Architecture Overview
39
+
40
+ This project is comprised by the following elements:
41
+
42
+ 1. `typing_trainer` - The binary to start the trainer
43
+ 2. `TypingTrainer` - The high level runner
44
+ 3. `TypingTrainer::Game` - Abstracts game mechanics and modes and screen
45
+ 4. `TypingTrainer::LevelGenerator` - Generates levels based on a layout
46
+ 5. `TypingTrainer::Level` - Represents a level: the text to use, instructions and settings
47
+ 6. `TypingTrainer::KeyboardLayout` - Defines finger mappings and letter progressions
48
+
49
+
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby -r rubygems
2
+ require 'typing_trainer'
3
+ require 'optparse'
4
+
5
+ options = {}
6
+
7
+ OptionParser.new do |opts|
8
+ opts.accept(Symbol) do |string|
9
+ string.to_sym
10
+ end
11
+
12
+ opts.banner = 'Usage: typing_trainer [options]'
13
+
14
+ opts.on('-t', '--letter LETTER', String, "A single letter to practice")
15
+
16
+ opts.on('-l', '--level LEVEL', Integer, "The starting level to play")
17
+
18
+ opts.on('-y', '--layout LAYOUT', Symbol, "The layout to use for finger hints and level progression")
19
+
20
+ opts.on('-f', '--file FILEPATH', String, "Path to file containing custom text to use")
21
+
22
+ opts.on('-a', '--advanced', TrueClass, "Hide finger help (default show)")
23
+
24
+ opts.on('-d', '--debug', TrueClass, "Show debugging messages (default hide)")
25
+ end.parse!(into: options)
26
+
27
+
28
+
29
+ TypingTrainer.run(options)
@@ -0,0 +1,32 @@
1
+ require "typing_trainer/version"
2
+
3
+ require "highline"
4
+ require "highline/system_extensions"
5
+ require "stty"
6
+ require "ffi"
7
+ require "termios"
8
+ require "typing_trainer/game"
9
+
10
+ module TypingTrainer
11
+
12
+ COLORS = HighLine::ColorScheme.new do |cs|
13
+ cs[:error] = [ :bold, :white, :on_red ]
14
+ cs[:correct] = [ :bold, :white, :on_green ]
15
+ end
16
+
17
+ HighLine::Style.new(:name=>:return, :builtin=>true, :code=>"\r")
18
+ HighLine::Style.new(:name=>:newline, :builtin=>true, :code=>"\n")
19
+ HighLine::Style.new(:name=>:up, :builtin=>true, :code=>"\e[A")
20
+ HighLine::Style.new(:name=>:down, :builtin=>true, :code=>"\e[B")
21
+ HighLine::Style.new(:name=>:forward, :builtin=>true, :code=>"\e[C")
22
+ HighLine::Style.new(:name=>:backward, :builtin=>true, :code=>"\e[D")
23
+
24
+ HighLine.color_scheme = COLORS
25
+
26
+ def self.run(options)
27
+ @game = Game.new(options)
28
+
29
+ @game.play!
30
+ end
31
+
32
+ end
@@ -0,0 +1,318 @@
1
+ require "typing_trainer/level_generator"
2
+ require "typing_trainer/level"
3
+
4
+ #
5
+ # Game
6
+ #
7
+ # Manages the game and internal state, checks if input is correct, prints information and handles
8
+ # screen cleanup
9
+ #
10
+ class TypingTrainer::Game
11
+ include HighLine::SystemExtensions
12
+
13
+ # Creates a new game
14
+ #
15
+ # @param [String] letter only words with that letter are shown and finger is shown.
16
+ # @param [Integer] level the level of the game, this is used to generate sentences. Overrides `letter`
17
+ # @param [String] file a file path to a text file for the game, overrides `level`
18
+ # @param [Array<String>] sentences the list of sentences to use for this game
19
+ # @param [Bool] advanced whether to show the finger hints or not
20
+ # @param [Symbol] layout the name of the layout to use. See TypingTrainer::KeyboardLayout
21
+ def initialize(level: nil, file: nil, sentences: nil, layout: :QWERTY, debug: false, advanced: false, letter: nil)
22
+ @layout = layout
23
+ @level_number = level
24
+ if !level && !letter
25
+ @level_number = 1
26
+ end
27
+ @file = file
28
+ @debug = debug
29
+ @advanced = advanced
30
+ @override_sentences = sentences
31
+ @introduce_letter = letter
32
+ @h = HighLine.new
33
+ end
34
+
35
+ def play!
36
+ @total_errors = 0
37
+ @errors = {}
38
+ @level = generate_level
39
+ @sentences = get_sentences
40
+ prepare_screen
41
+ before = Time.now
42
+ @sentences.each do |sentence|
43
+ print_level_header
44
+ show_sentence(sentence)
45
+ clean_screen
46
+ end
47
+ show_result(before)
48
+ rescue SystemExit, Interrupt
49
+ reset_terminal
50
+ rescue Exception => e
51
+ reset_terminal
52
+ raise e
53
+ end
54
+
55
+ private
56
+
57
+ def reset_terminal
58
+ clean_screen
59
+ @h.restore_mode
60
+ puts "Thank you for playing."
61
+ puts "Good bye!"
62
+ print :clear
63
+ print :erase_line
64
+ end
65
+
66
+ def generate_level
67
+ @level = if @override_sentences
68
+ TypingTrainer::Level.new(sentences: @override_sentences, layout: @layout)
69
+ elsif @file
70
+ TypingTrainer::Level.new(
71
+ sentences: open(@file).readlines.map {|l| l.strip!; l == "" ? nil : l;}.compact,
72
+ filename: @file,
73
+ layout: @layout
74
+ )
75
+ elsif @introduce_letter
76
+ TypingTrainer::LevelGenerator.generate(letters: [@introduce_letter], layout: @layout)
77
+ else
78
+ TypingTrainer::LevelGenerator.generate(level: @level_number, layout: @layout)
79
+ end
80
+ end
81
+
82
+ def get_sentences
83
+ @level.sentences
84
+ end
85
+
86
+ def get_character
87
+ @h.get_character
88
+ end
89
+
90
+ def show_sentence(sentence)
91
+ @cursor = 0
92
+ original = sentence
93
+ @typed = ""
94
+
95
+ print original
96
+ print :newline
97
+ print_hands(finger: @level.finger(original[@cursor])) if @introduce_letter
98
+ $stdout.flush
99
+
100
+ while c = get_character.chr do
101
+ # puts "CHAR: #{c}"
102
+ typed_error = false
103
+ case c
104
+ when "\x7F" # backspace
105
+ delete_char
106
+ print_hands(finger: @level.finger(original[@cursor]))
107
+ @cursor.times do
108
+ print :forward
109
+ end
110
+ next
111
+ when original[@cursor] # good
112
+ print c, :correct
113
+ else # error
114
+ log_error(c)
115
+ typed_error = true
116
+ end
117
+
118
+
119
+ @typed += c
120
+ @cursor += 1
121
+ finished = original == @typed
122
+ should_show_hands = !finished && original[@cursor] && (@introduce_letter || typed_error)
123
+ if (should_show_hands)
124
+ print_hands(finger: @level.finger(original[@cursor]))
125
+ @cursor.times do
126
+ print :forward
127
+ end
128
+ elsif !finished
129
+ hide_hands
130
+ @cursor.times do
131
+ print :forward
132
+ end
133
+ end
134
+
135
+ typed_error = false
136
+
137
+ if original == @typed
138
+ break
139
+ end
140
+ end
141
+ end
142
+
143
+ # Prints hands with finger hints
144
+ def print_hands(finger: nil)
145
+ print :newline
146
+ print HANDS
147
+ (HANDS.lines.count + 1).times do
148
+ print :up
149
+ end
150
+ print_finger(finger)
151
+ end
152
+
153
+ # Erases the lines showing finger hints
154
+ def hide_hands
155
+ print :newline
156
+ (HANDS.lines.count + 1).times do
157
+ print :erase_line
158
+ puts
159
+ end
160
+ (HANDS.lines.count + 1).times do
161
+ print :up
162
+ end
163
+ print :up
164
+ end
165
+
166
+ # Finds which finger has to be highlighted and prints
167
+ # a white block inside it.
168
+ def print_finger(finger)
169
+ finger_character = '█'
170
+ finger_coords = {
171
+ LEFT_P: [2,1],
172
+ LEFT_R: [1,3],
173
+ LEFT_M: [1,5],
174
+ LEFT_I: [1,7],
175
+ LEFT_T: [4,10],
176
+ RIGHT_P: [2,28],
177
+ RIGHT_R: [1,26],
178
+ RIGHT_M: [1,24],
179
+ RIGHT_I: [1,22],
180
+ RIGHT_T: [4,19]
181
+ }
182
+
183
+ coords = finger_coords[finger]
184
+ if (coords)
185
+ print :down
186
+ coords[0].times { print :down }
187
+ coords[1].times { print :forward }
188
+ print finger_character
189
+ print :backward
190
+ coords[1].times { print :backward }
191
+ coords[0].times { print :up }
192
+ print :up
193
+ end
194
+ end
195
+
196
+ # Print results of last played game.
197
+ def show_result(started_at)
198
+ @h.restore_mode
199
+ total_chars = @sentences.join.size
200
+ accuracy = "#{((total_chars - @total_errors) * 100.0 / total_chars).round(2)}%"
201
+ elapsed_minutes = (Time.now - started_at)/60
202
+ total_words = @sentences.join.split(' ').size
203
+ # wpm = "#{total_words/elapsed_minutes.to_f} WPM"
204
+ ccpm = "#{((total_chars)/elapsed_minutes.to_f).floor/5} WPM / #{((total_chars)/elapsed_minutes.to_f).floor} CPM"
205
+
206
+ print "These are your results:\n", :bold
207
+ print "Total time: #{elapsed_minutes} minutes\n", :bold
208
+ print "Character count: #{total_chars}\n", :bold
209
+ print 'Speed: ', :blue
210
+ print ccpm, :bold
211
+ print ' '
212
+ print 'Accuracy: ', :green
213
+ print accuracy, :bold
214
+ print ' '
215
+ print 'Errors: ', :red
216
+ print "#{@total_errors}/#{total_chars}", :bold
217
+ if @h.ask("\n\nDo you want to play again? [y,n]", ["y", "n"]) == "y"
218
+ # The logic ahead allows us to support starting from a letter or a level indistictively and
219
+ # continue with the next levels and letters as expected
220
+ if @introduce_letter
221
+ @level_number = @level.next_level(@introduce_letter)
222
+ if @level_number == 1
223
+ # If they only know one letter, don't make them repeat it. Introduce one more instead
224
+ @introduce_letter = @level.next_letter(1)
225
+ else
226
+ @introduce_letter = nil
227
+ end
228
+ elsif @level_number
229
+ @introduce_letter = @level.next_letter(@level_number)
230
+ end
231
+ play!
232
+ end
233
+ end
234
+
235
+ # Our own print to override the system one.
236
+ # This uses HighLine to output special characters
237
+ # If the parameter is a symbol, it will use
238
+ # HighLine.Style() and use its code
239
+ def print(str_or_sym, color = :none)
240
+ s = case str_or_sym
241
+ when String
242
+ @h.color(str_or_sym, color)
243
+ when Symbol
244
+ HighLine.Style(str_or_sym).code
245
+ end
246
+ $stdout.print(s)
247
+ end
248
+
249
+ # Ensures the screen has all the lines that the terminal has
250
+ # to move freely without needing to create more lines.
251
+ def prepare_screen
252
+ # Ensure we restore mode bedore setting to no_echo
253
+ # otherwise we lose context and cannot go back
254
+ @h.restore_mode rescue
255
+ print :erase_line
256
+ @h.output_rows.times do
257
+ print :newline
258
+ end
259
+ @h.output_rows.times do
260
+ print :up
261
+ end
262
+ @h.raw_no_echo_mode
263
+ end
264
+
265
+ # Uses the level information to print
266
+ # a header while a game is underway.
267
+ def print_level_header
268
+ print @level.header(@h.output_cols)
269
+ if @debug
270
+ puts({
271
+ layout: @layout,
272
+ level: @level_number,
273
+ letter: @introduce_letter,
274
+ advanced: @advanced,
275
+ cursor: @cursor
276
+ }).inspect
277
+ end
278
+ end
279
+
280
+ # Uses a clear screen escape sequence
281
+ # and prepares the screen
282
+ def clean_screen
283
+ print "\e[2J"
284
+ prepare_screen
285
+ end
286
+
287
+ # Deletes a character in the input "bar"
288
+ def delete_char
289
+ print :backward
290
+ print :erase_char
291
+ @cursor -= 1 if @cursor > 0
292
+ @typed = @typed[0..-2] if @typed.size > 0
293
+ end
294
+
295
+ # Changes the color of the input "bar" to show
296
+ # the user didn't type the right character
297
+ def log_error(c)
298
+ print c, :error
299
+ @total_errors += 1
300
+ @errors[c] ||= 0
301
+ @errors[c] += 1
302
+ end
303
+
304
+ # ASCII Hands. We need to escape \ because it is
305
+ # a special character for strings.
306
+ HANDS = <<~HANDS
307
+ _.-._ _.-._
308
+ _| | | | | | | |_
309
+ | | | | | | | | | |
310
+ | | | | | /\\ /\\ | | | | |
311
+ | |/ / \\ \\| |
312
+ | / / \\ \\ |
313
+ | / \\ |
314
+ \\ / \\ /
315
+ | | | |
316
+ HANDS
317
+
318
+ end
@@ -0,0 +1,7 @@
1
+ require 'typing_trainer/keyboard_layout/qwerty'
2
+
3
+ module TypingTrainer::KeyboardLayout
4
+ LAYOUTS = {
5
+ QWERTY: Qwerty
6
+ }
7
+ end
@@ -0,0 +1,37 @@
1
+ #require "typing_trainer/keyboard_layout"
2
+
3
+ module TypingTrainer::KeyboardLayout
4
+ class Qwerty
5
+ LETTER_PROGRESSION = "asdfjklghruvmbntyeiwoqpxz"
6
+
7
+ FINGER = {
8
+ 'a' => :LEFT_P,
9
+ 'b' => :LEFT_I,
10
+ 'c' => :LEFT_M,
11
+ 'd' => :LEFT_M,
12
+ 'e' => :LEFT_M,
13
+ 'f' => :LEFT_I,
14
+ 'g' => :LEFT_I,
15
+ 'h' => :RIGHT_I,
16
+ 'i' => :RIGHT_M,
17
+ 'j' => :RIGHT_I,
18
+ 'k' => :RIGHT_M,
19
+ 'l' => :RIGHT_R,
20
+ 'm' => :RIGHT_I,
21
+ 'n' => :RIGHT_I,
22
+ 'o' => :RIGHT_R,
23
+ 'p' => :RIGHT_P,
24
+ 'q' => :LEFT_P,
25
+ 'r' => :LEFT_I,
26
+ 's' => :LEFT_R,
27
+ 't' => :LEFT_I,
28
+ 'u' => :RIGHT_I,
29
+ 'v' => :LEFT_I,
30
+ 'w' => :LEFT_R,
31
+ 'x' => :LEFT_R,
32
+ 'y' => :RIGHT_I,
33
+ 'z' => :LEFT_P,
34
+ ' ' => :LEFT_T
35
+ }
36
+ end
37
+ end
@@ -0,0 +1,44 @@
1
+ require "typing_trainer/keyboard_layout"
2
+
3
+ class TypingTrainer::Level
4
+ attr_reader :sentences
5
+ attr_reader :number
6
+ attr_reader :letters
7
+
8
+ def initialize(number: nil, letters: nil, sentences: [], filename: nil, layout: nil)
9
+ throw "Missing layout!" unless layout
10
+ throw "Missing sentences!" unless sentences.count > 0
11
+ @number = number
12
+ @letters = letters
13
+ @sentences = sentences
14
+ @layout_id = layout
15
+ layouts = TypingTrainer::KeyboardLayout::LAYOUTS
16
+ @layout = layouts[layout]
17
+ end
18
+
19
+ def header(width)
20
+ if @number
21
+ puts "Playing Level #{@number}"
22
+ puts "-"*width
23
+ puts
24
+ elsif @letters && @letters.size == 1
25
+ puts "New letter: #{@letters[0].upcase}"
26
+ puts "-"*width
27
+ puts
28
+ end
29
+ end
30
+
31
+ def finger(letter)
32
+ @layout::FINGER[letter.downcase]
33
+ end
34
+
35
+ # What is the next level if you know up to this letter?
36
+ def next_level(letter)
37
+ @layout::LETTER_PROGRESSION.index(letter.downcase) + 1
38
+ end
39
+
40
+ # What is the next letter to learn after beating this level?
41
+ def next_letter(level)
42
+ @layout::LETTER_PROGRESSION[level]
43
+ end
44
+ end
@@ -0,0 +1,51 @@
1
+ require 'contemporary_words'
2
+ require "typing_trainer/level"
3
+ require "typing_trainer/keyboard_layout"
4
+
5
+ class TypingTrainer::LevelGenerator
6
+
7
+ SENCENCES_PER_LEVEL = 3
8
+ WORDS_PER_SENTENCE = 8
9
+
10
+ def self.generate(level: nil, letters: nil, layout: nil)
11
+ throw "You need to specify a layout" unless layout
12
+ throw "No level or letters were provided to generate a Game level :(" unless level || letters
13
+
14
+ layouts = TypingTrainer::KeyboardLayout::LAYOUTS
15
+
16
+ layout_letters = layouts[layout]::LETTER_PROGRESSION
17
+ if letters
18
+ letters = letters.join('').downcase
19
+ end
20
+ letters ||= layout_letters.slice(0, level)
21
+ sentences = self.new(letters).generate_sentences(SENCENCES_PER_LEVEL)
22
+ TypingTrainer::Level.new(number: level, letters: letters, sentences: sentences, layout: layout)
23
+ end
24
+
25
+ def initialize(letters)
26
+ @letters = letters.split(//)
27
+ @level_pattern = /^[#{letters}]+$/
28
+ @words = initialize_words
29
+ end
30
+
31
+ def initialize_words
32
+ words = ContemporaryWords.all.filter { |word| @level_pattern.match?(word) }
33
+ while words.size < WORDS_PER_SENTENCE
34
+ words << self.generate_word
35
+ end
36
+ words
37
+ end
38
+
39
+ def generate_sentences(count)
40
+ sentences = []
41
+ count.times {
42
+ sentences << @words.sample(WORDS_PER_SENTENCE).join(' ') + ' '
43
+ }
44
+ sentences
45
+ end
46
+
47
+ def generate_word
48
+ (@letters * 10).sample(rand(5) + 1).join('')
49
+ end
50
+
51
+ end
@@ -0,0 +1,3 @@
1
+ module TypingTrainer
2
+ VERSION = "0.1.0"
3
+ end
Binary file
@@ -0,0 +1,41 @@
1
+ require 'typing_trainer'
2
+ require 'typing_trainer/game'
3
+
4
+ describe TypingTrainer::Game, '#play!' do
5
+
6
+ before :each do
7
+ game.stub(:prepare_screen)
8
+ game.stub(:show_result)
9
+ end
10
+
11
+ let(:game) { TypingTrainer::Game.new(sentences: ["first", "second"], layout: :QWERTY) }
12
+
13
+ it "shows each sentence" do
14
+ game.should_receive(:show_sentence).exactly(2)
15
+ game.should_receive(:clean_screen).exactly(2)
16
+ game.should_receive(:show_result)
17
+ game.play!
18
+ end
19
+
20
+ it "reads the characters" do
21
+ game.should_receive(:get_character).and_return(*%w{f i r s t s e c o n d})
22
+ game.play!
23
+ end
24
+
25
+ it "handles delete" do
26
+ input = %w{f i r s x} + [[0x7F].pack('U')] + %w{t s e c o n d}
27
+
28
+ game.should_receive(:get_character).and_return(*input)
29
+ game.should_receive(:show_result)
30
+ game.play!
31
+ end
32
+
33
+ it "handles delete at beginning" do
34
+ input = [[0x7F].pack('U')]*3 + %w{f i r s t} + %w{s e c o n d}
35
+
36
+ game.should_receive(:get_character).and_return(*input)
37
+ game.should_receive(:show_result)
38
+ game.play!
39
+ end
40
+
41
+ end
@@ -0,0 +1,16 @@
1
+ require 'typing_trainer'
2
+ require 'typing_trainer/level_generator'
3
+
4
+ describe TypingTrainer::LevelGenerator, '#generate' do
5
+
6
+ it "should generate 5 sentences per level" do
7
+ level = TypingTrainer::LevelGenerator.generate(level: 1, layout: :QWERTY);
8
+ level.sentences.size.should be 4
9
+ end
10
+
11
+ it "should generate 8 words per sentence" do
12
+ level = TypingTrainer::LevelGenerator.generate(level: 1, layout: :QWERTY);
13
+ level.sentences[0].split(' ').size.should be 8
14
+ end
15
+
16
+ end
@@ -0,0 +1,4 @@
1
+ teas to the toad
2
+ To the inn that is not haunted
3
+ this is not a house
4
+ In a neat toasted seed has it needed as a hot
@@ -0,0 +1,5 @@
1
+ 44444 77777 4444 7777 444 777 47474
2
+ 74747 4477 7744 4747 7474 4 7 47 74
3
+ 33333 88888 3333 8888 333 888 38383
4
+ 83838 3388 8833 3838 8383 3 8 38 83
5
+ 384738 37483748 73487
@@ -0,0 +1,8 @@
1
+ A Crow having stolen a bit of meat, perched in a tree and held it in her beak.
2
+ A Fox, seeing this, longed to possess the meat himself, and by a wily stratagem succeeded.
3
+ 'How handsome is the Crow,' he exclaimed, in the beauty of her shape and in the fairness of her complexion!
4
+ Oh, if her voice were only equal to her beauty, she would deservedly be considered the Queen of Birds!'
5
+ This he said deceitfully but the Crow, anxious to refute the reflection cast upon her voice,
6
+ set up a loud caw and dropped the flesh.
7
+ The Fox quickly picked it up, and thus addressed the Crow:
8
+ 'My good Crow, your voice is right enough, but your wit is wanting.'
@@ -0,0 +1,5 @@
1
+ A Hound started a Hare from his lair, but after a long run, gave up the chase.
2
+ A goat-herd seeing him stop, mocked him, saying
3
+ 'The little one is the best runner of the two.'
4
+ The Hound replied, 'You do not see the difference between us:
5
+ I was only running for a dinner, but he for his life.'
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/typing_trainer/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ['Juan Germán Castañeda Echevarría']
6
+ gem.email = ['juanger@gmail.com']
7
+ gem.description = %q{Ruby typing trainer}
8
+ gem.summary = %q{Improve your typing skills by completing the tutorial or using your own text or code!}
9
+ gem.homepage = ''
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = 'typing_trainer'
15
+ gem.require_paths = ['lib']
16
+ gem.version = TypingTrainer::VERSION
17
+ gem.add_dependency 'highline', '~> 1.7.3'
18
+ gem.add_dependency 'ffi-ncurses'
19
+ gem.add_dependency 'ruby-termios'
20
+ gem.add_dependency 'stty'
21
+ gem.add_dependency 'contemporary_words'
22
+ gem.add_development_dependency 'rspec'
23
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: typing_trainer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Juan Germán Castañeda Echevarría
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-03-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: highline
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.7.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.7.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: ffi-ncurses
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ruby-termios
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: stty
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: contemporary_words
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Ruby typing trainer
98
+ email:
99
+ - juanger@gmail.com
100
+ executables:
101
+ - typing_trainer
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".ruby-version"
107
+ - Gemfile
108
+ - LICENSE
109
+ - README.md
110
+ - Rakefile
111
+ - bin/typing_trainer
112
+ - lib/typing_trainer.rb
113
+ - lib/typing_trainer/game.rb
114
+ - lib/typing_trainer/keyboard_layout.rb
115
+ - lib/typing_trainer/keyboard_layout/qwerty.rb
116
+ - lib/typing_trainer/level.rb
117
+ - lib/typing_trainer/level_generator.rb
118
+ - lib/typing_trainer/version.rb
119
+ - screenshots/playing.png
120
+ - spec/game_spec.rb
121
+ - spec/level_generator_spec.rb
122
+ - text/dvorak/home.txt
123
+ - text/dvorak/numbers.txt
124
+ - text/fox_and_crow.txt
125
+ - text/hare_and_hound.txt
126
+ - typing_trainer.gemspec
127
+ homepage: ''
128
+ licenses: []
129
+ metadata: {}
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubygems_version: 3.0.3
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Improve your typing skills by completing the tutorial or using your own text
149
+ or code!
150
+ test_files:
151
+ - spec/game_spec.rb
152
+ - spec/level_generator_spec.rb