need_for_type 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
+ SHA1:
3
+ metadata.gz: c9d15376668b2067a3279a2f17c32f62e209cc6c
4
+ data.tar.gz: b9c778b6ced2d63ae6f7f233a494ac274630e644
5
+ SHA512:
6
+ metadata.gz: 6f8cb2a5d6424054fcb74bbc0635b05c0616ae803dac320ee10313e222237350f5d7ed5602b09fabe04bf7a3dca0f9305ee2b796d772cfef3f13a8a21c103ce1
7
+ data.tar.gz: 9a4e563fcf77a6ca9ab0bb28956e8350413f95674a2bdf21d506c5273102bd2076d7a47e4c918b3b9b6236d8404ea634fbd5c5cabe713ce8b90d6ca88be9647a
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in need_for_type.gemspec
4
+ gemspec
5
+
6
+ gem 'curses'
7
+ gem 'rubocop'
@@ -0,0 +1,48 @@
1
+ # need-for-type
2
+
3
+ need-for-type is a terminal type racer inpired by [Nitro Type](https://www.nitrotype.com/).
4
+
5
+ ![screenshot](https://raw.githubusercontent.com/jbernardo95/need-for-type/master/assets/screenshot.png)
6
+
7
+ ## Installation
8
+
9
+ Run the following command:
10
+
11
+ ```shell
12
+ $ gem install 'need_for_type'
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ On a new terminal window run the following command:
18
+
19
+ ```shell
20
+ $ need_for_type
21
+ ```
22
+
23
+ ## Development
24
+
25
+ After checking out the repo, run `./bin/setup` to install dependencies.
26
+
27
+ You can run `./bin/console` for an interactive prompt that will allow you to experiment.
28
+
29
+ To install the development version of the gem onto your local machine, run `bundle exec rake install`.
30
+
31
+ To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+
34
+ ## Contributing
35
+
36
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jbernardo95/need-for-type. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
37
+
38
+
39
+ ## Credits
40
+
41
+ Sample texts included in this library come from the following sources:
42
+
43
+ - http://www.lipsum.com/
44
+ - https://medium.com/@kristicoulter
45
+
46
+ ## License
47
+
48
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1 @@
1
+ There is. But it's broken. I mingle empty-handed for 15 minutes, fending off well-meaning offers to get me something from the bar. After the fifth, I realize I'm going to cry if one more person offers me alcohol. I leave and cry anyway. Later I order vanilla ice cream from room service to cheer myself up.
@@ -0,0 +1 @@
1
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam suscipit porttitor arcu, eget hendrerit est iaculis a. Aliquam faucibus libero et accumsan accumsan. Duis id tellus eu tellus finibus eleifend. Sed quis elit nec metus condimentum ultricies vitae in est. Proin sit amet scelerisque odio. Aenean luctus lorem non ante hendrerit luctus. Pellentesque et tellus id lectus ornare mattis. Aenean ac dolor vitae metus iaculis mollis eu vel arcu. Donec aliquet turpis diam, sit amet finibus enim vulputate mattis. Quisque laoreet feugiat nisi eget tempus. Duis varius dapibus nulla. Etiam at elit sapien. Pellentesque dui odio, ornare id nisl elementum, consectetur hendrerit turpis. Quisque convallis sapien orci. Mauris non enim sollicitudin, fringilla justo quis, congue massa. Etiam cursus, tellus interdum mattis lacinia, urna magna luctus mauris, at egestas ante tortor a urna.
@@ -0,0 +1 @@
1
+ That second summer, I meet my friend Mindy outside San Diego, where her adopted son is days from being born. Mindy’s dark alleys were different from mine, but she walked them all the same and walked herself out of them, too. Sometimes, talking about the recent past, we blink at each other like people struggling to readjust to sunlight after a long, bad movie. More and more it’s the new that gets our attention: my new job, her newish and happy marriage, the book I’m writing and the classes she’s taking. The things we are making happen, step by step.
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'curses'
4
+ require 'need_for_type'
5
+ require 'need_for_type/typer'
6
+ require 'need_for_type/window'
7
+
8
+ Curses.init_screen
9
+ Curses.noecho
10
+ NeedForType::Window.init_colors
11
+
12
+ begin
13
+ typer = NeedForType::Typer.new
14
+ typer.play
15
+ ensure
16
+ Curses.close_screen
17
+ end
@@ -0,0 +1,5 @@
1
+ require 'curses'
2
+
3
+ module NeedForType
4
+ GEM_ROOT = File.expand_path("../..", __FILE__)
5
+ end
@@ -0,0 +1,81 @@
1
+ require 'curses'
2
+ require 'need_for_type/window'
3
+
4
+ module NeedForType
5
+ class DisplayWindow < Window
6
+
7
+ def initialize
8
+ super(Curses.lines, Curses.cols, 0, 0)
9
+ Curses.curs_set(CURSOR_INVISIBLE)
10
+ end
11
+
12
+ def render_menu(selected_option)
13
+ self.render do
14
+ self.setpos(2, 4)
15
+ self.render_text("Need For Type", GREEN, NORMAL)
16
+
17
+ self.setpos(4, 4)
18
+ mode = standout_mode(0, selected_option)
19
+ self.render_text("1. Easy", WHITE, mode)
20
+
21
+ self.setpos(5, 4)
22
+ mode = standout_mode(1, selected_option)
23
+ self.render_text("2. Medium", WHITE, mode)
24
+
25
+ self.setpos(6, 4)
26
+ mode = standout_mode(2, selected_option)
27
+ self.render_text("3. Hard", WHITE, mode)
28
+
29
+ self.setpos(8, 4)
30
+ mode = standout_mode(3, selected_option)
31
+ self.render_text("Exit", WHITE, mode)
32
+ end
33
+ end
34
+
35
+ def standout_mode(option, selected_option)
36
+ selected_option == option ? STANDOUT : NORMAL
37
+ end
38
+
39
+ def render_game_text(text, chars_completed, failed = false)
40
+ current_color = failed ? RED : YELLOW
41
+ completed = text[0, chars_completed - 1]
42
+ tail = text[chars_completed + 1, text.length]
43
+
44
+ self.render do
45
+ self.setpos(2,4)
46
+ self.render_multiple_text(GREEN, NORMAL) { completed.each { |c| self.addstr(c) } }
47
+ self.render_text(c, color, NORMAL)
48
+ self.render_multiple_text(WHITE, NORMAL) { tail.each { |c| self.addstr(c) } }
49
+ end
50
+ end
51
+
52
+ def render_game_text(text, chars_completed, failed = false)
53
+ self.render do
54
+ self.setpos(2, 4)
55
+ text.each_with_index do |c, i|
56
+ if i < chars_completed
57
+ self.render_text(c, GREEN, NORMAL)
58
+ elsif i == chars_completed
59
+ color = failed ? RED : YELLOW
60
+ self.render_text(c, color, NORMAL)
61
+ else
62
+ self.render_text(c, WHITE, NORMAL)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ def render_score(time, wpm, accuracy)
69
+ self.render do
70
+ self.setpos(2, 4)
71
+ self.render_text("Time: #{time.round(2)} sec", GREEN, NORMAL)
72
+
73
+ self.setpos(3, 4)
74
+ self.render_text("WPM: #{wpm.round}", GREEN, NORMAL)
75
+
76
+ self.setpos(4, 4)
77
+ self.render_text("Accuracy: #{accuracy.round(2)} %", GREEN, NORMAL)
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,31 @@
1
+ module NeedForType
2
+ class FileManager
3
+ attr_accessor :content
4
+
5
+ def initialize(difficulty)
6
+ @path = "#{NeedForType::GEM_ROOT}/assets/#{difficulty_to_s(difficulty)}/"
7
+ end
8
+
9
+ # Reads random file from difficulty folder
10
+ def get_random_text
11
+ directory = Dir[@path]
12
+ random_file = rand(1..directory.count)
13
+
14
+ # TODO: apply markov's chain's to the text
15
+ @content = File.read("#{@path}/#{random_file}.txt").strip
16
+ end
17
+
18
+ private
19
+
20
+ def difficulty_to_s(difficulty)
21
+ case difficulty
22
+ when 0
23
+ 'easy'
24
+ when 1
25
+ 'medium'
26
+ when 2
27
+ 'hard'
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ require 'need_for_type/states/state'
2
+ require 'need_for_type/states/menu'
3
+ require 'need_for_type/states/game'
4
+ require 'need_for_type/states/score'
5
+
6
+ module NeedForType
7
+ module States
8
+ end
9
+ end
@@ -0,0 +1,113 @@
1
+ require 'curses'
2
+
3
+ require 'need_for_type/states'
4
+ require 'need_for_type/file_manager'
5
+
6
+ module NeedForType::States
7
+ class Game < State
8
+
9
+ def initialize(display_window, difficulty)
10
+ super(display_window)
11
+
12
+ @difficulty = difficulty
13
+ @state = :init_game
14
+ @text = ''
15
+ end
16
+
17
+ # Takes action according to the current @state
18
+ def update
19
+ case @state
20
+ when :init_game
21
+ handle_init_game
22
+ when :in_game_get_input
23
+ handle_in_game_get_input
24
+ when :in_game_valid_input
25
+ handle_in_game_valid_input
26
+ when :in_game_invalid_input
27
+ handle_in_game_invalid_input
28
+ when :end_game
29
+ handle_end_game
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def handle_init_game
36
+ @chars_completed = 0
37
+ @word = ''
38
+
39
+ @total_taps = 0
40
+ @correct_taps = 0
41
+
42
+ file_manager = NeedForType::FileManager.new(@difficulty)
43
+ @text = file_manager.get_random_text
44
+ @split_text = @text.split('')
45
+ @display_window.render_game_text(@split_text, @chars_completed)
46
+
47
+ @start_time = Time.now
48
+
49
+ @state = :in_game_get_input
50
+
51
+ return self
52
+ end
53
+
54
+ # Gets input from user and compares it
55
+ def handle_in_game_get_input
56
+ input = @display_window.get_input
57
+ @total_taps += 1
58
+
59
+ if input == @text[@chars_completed]
60
+ @state = :in_game_valid_input
61
+ else
62
+ @state = :in_game_invalid_input
63
+ end
64
+
65
+ return self
66
+ end
67
+
68
+ # User input is correct
69
+ def handle_in_game_valid_input
70
+ @chars_completed += 1
71
+ @correct_taps += 1
72
+
73
+ if @chars_completed == @text.size
74
+ @state = :end_game
75
+ return self
76
+ end
77
+
78
+ if @text[@chars_completed] == ' '
79
+ @word = ''
80
+ else
81
+ @word += @text[@chars_completed]
82
+ end
83
+
84
+ # Render
85
+ @display_window.render_game_text(@split_text, @chars_completed)
86
+
87
+ @state = :in_game_get_input
88
+
89
+ return self
90
+ end
91
+
92
+ # User input is wrong
93
+ def handle_in_game_invalid_input
94
+ Curses.beep
95
+
96
+ @display_window.render_game_text(@split_text, @chars_completed, true)
97
+
98
+ @state = :in_game_get_input
99
+
100
+ return self
101
+ end
102
+
103
+ def handle_end_game
104
+ @end_time = Time.now
105
+
106
+ total_time = @end_time - @start_time
107
+ wpm = (@text.split.size * 60) / total_time
108
+ accuracy = (@correct_taps.to_f / @total_taps.to_f) * 100
109
+
110
+ return NeedForType::States::Score.new(@display_window, total_time, wpm, accuracy)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,31 @@
1
+ require 'curses'
2
+
3
+ require 'need_for_type/states'
4
+
5
+ module NeedForType::States
6
+ class Menu < State
7
+
8
+ def initialize(display_window)
9
+ super(display_window)
10
+
11
+ @option = 0
12
+ end
13
+
14
+ def update
15
+ @display_window.render_menu(@option)
16
+
17
+ input = @display_window.get_input
18
+
19
+ if input == Curses::Key::UP
20
+ @option = (@option - 1) % 4
21
+ elsif input == Curses::Key::DOWN
22
+ @option = (@option + 1) % 4
23
+ elsif input == Curses::Key::ENTER || input == 10
24
+ exit if @option == 3
25
+ return NeedForType::States::Game.new(@display_window, @option)
26
+ end
27
+
28
+ return self
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ require 'curses'
2
+
3
+ require 'need_for_type/states'
4
+
5
+ module NeedForType::States
6
+ class Score < State
7
+
8
+ def initialize(display_window, time, wpm, accuracy)
9
+ super(display_window)
10
+ @time = time
11
+ @wpm = wpm
12
+ @accuracy = accuracy
13
+ end
14
+
15
+ def update
16
+ @display_window.render_score(@time, @wpm, @accuracy)
17
+
18
+ input = @display_window.get_input
19
+
20
+ if input == Curses::Key::ENTER || input == 10
21
+ return NeedForType::States::Menu.new(@display_window)
22
+ end
23
+
24
+ return self
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ module NeedForType::States
2
+ class State
3
+
4
+ def initialize(display_window)
5
+ @display_window = display_window
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ require 'curses'
2
+
3
+ require 'need_for_type/display_window'
4
+
5
+ require 'need_for_type/states'
6
+
7
+ module NeedForType
8
+ class Typer
9
+
10
+ def initialize
11
+ display_window = NeedForType::DisplayWindow.new
12
+
13
+ @state = NeedForType::States::Menu.new(display_window)
14
+ end
15
+
16
+ def play
17
+ while true
18
+ @state = @state.update
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,3 @@
1
+ module NeedForType
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,62 @@
1
+ require 'curses'
2
+
3
+ module NeedForType
4
+ class Window < Curses::Window
5
+ # Cursor visibility
6
+ CURSOR_VISIBLE = 1
7
+ CURSOR_INVISIBLE = 0
8
+
9
+ # Colors
10
+ WHITE = 1
11
+ GREEN = 2
12
+ YELLOW = 3
13
+ RED = 4
14
+
15
+ # Modes
16
+ NORMAL = Curses::A_NORMAL
17
+ STANDOUT = Curses::A_STANDOUT
18
+
19
+ def initialize(lines, cols, top, left)
20
+ super(lines, cols, top, left)
21
+
22
+ self.box('|', '-')
23
+ self.keypad = true
24
+ self.refresh
25
+ end
26
+
27
+ def self.init_colors
28
+ Curses.start_color
29
+ Curses.init_pair(WHITE, Curses::COLOR_WHITE, Curses::COLOR_BLACK)
30
+ Curses.init_pair(GREEN, Curses::COLOR_GREEN, Curses::COLOR_BLACK)
31
+ Curses.init_pair(YELLOW, Curses::COLOR_YELLOW, Curses::COLOR_BLACK)
32
+ Curses.init_pair(RED, Curses::COLOR_RED, Curses::COLOR_BLACK)
33
+ end
34
+
35
+ def render(color = WHITE, mode = NORMAL)
36
+ self.attrset(Curses.color_pair(color) | mode)
37
+
38
+ self.clear
39
+ self.box('|', '-')
40
+
41
+ yield if block_given?
42
+
43
+ self.refresh
44
+ end
45
+
46
+ def render_text(text, color = WHITE, mode = NORMAL)
47
+ self.attron(Curses.color_pair(color) | mode)
48
+ self.addstr(text)
49
+ self.attroff(Curses.color_pair(color) | mode)
50
+ end
51
+
52
+ def render_multiple_text(color = WHITE, mode = NORMAL)
53
+ self.attron(Curses.color_pair(color) | mode)
54
+ yield
55
+ self.attroff(Curses.color_pair(color) | mode)
56
+ end
57
+
58
+ def get_input
59
+ self.getch
60
+ end
61
+ end
62
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: need_for_type
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - tiagonbotelho
8
+ - jbernardo95
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-08-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '1.10'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '1.10'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '10.0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '10.0'
42
+ description: need-for-type is a terminal type racer inpired by Nitro Type
43
+ email:
44
+ - tiagonbotelho@gmail.com
45
+ - jbamaro95@gmail.com
46
+ executables:
47
+ - need_for_type
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - Gemfile
52
+ - README.md
53
+ - assets/easy/1.txt
54
+ - assets/hard/1.txt
55
+ - assets/medium/1.txt
56
+ - bin/need_for_type
57
+ - lib/need_for_type.rb
58
+ - lib/need_for_type/display_window.rb
59
+ - lib/need_for_type/file_manager.rb
60
+ - lib/need_for_type/states.rb
61
+ - lib/need_for_type/states/game.rb
62
+ - lib/need_for_type/states/menu.rb
63
+ - lib/need_for_type/states/score.rb
64
+ - lib/need_for_type/states/state.rb
65
+ - lib/need_for_type/typer.rb
66
+ - lib/need_for_type/version.rb
67
+ - lib/need_for_type/window.rb
68
+ homepage: https://github.com/jbernardo95/need-for-type
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.6.6
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: A terminal typeracer
92
+ test_files: []