basher-basher 0.1.1

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: 0f7643a1d5341d906390a12466992ecbcd7d9e8e
4
+ data.tar.gz: 4b0a87dd94139d10934e4ba9dc10827f9ebceee7
5
+ SHA512:
6
+ metadata.gz: 6913dfa636e802e50daea0bcc31aa0939c0b4a6bbff54bd367d45246d13962969166a9eb0e3e21335f67b038f83d101c79970cd24063821da546c2f968bdad2f
7
+ data.tar.gz: 99c6d8205450f186cd32a018c671c5d4d97b1c3657c5c1db76543bcdf6eff74895ff6e647f34b94752e51b9b205c106e7dec0fcb60fca791b0d336bd7313b8a6
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.sublime*
11
+
12
+ /experiments
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in basher.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Stefan Rotariu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,37 @@
1
+ # Basher
2
+
3
+ Basher is a small CLI game that tests your typing speed.
4
+
5
+ [![asciicast](https://asciinema.org/a/36151.png)](https://asciinema.org/a/36151)
6
+
7
+ ## Installation
8
+
9
+ ```shell
10
+ $ gem install basher-basher
11
+ ```
12
+
13
+ ## Requirements
14
+
15
+ * Ruby 2.3
16
+
17
+ ## Usage
18
+
19
+ ```shell
20
+ $ basher -h
21
+ ```
22
+
23
+ ## Development
24
+
25
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
+
27
+ To install this gem onto your local machine, run `bundle exec rake install`. 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).
28
+
29
+ ## Contributing
30
+
31
+ Bug reports and pull requests are welcome on GitHub at https://github.com/shuriu/basher.
32
+
33
+
34
+ ## License
35
+
36
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
37
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,29 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'basher/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "basher-basher"
8
+ spec.version = Basher::VERSION
9
+ spec.authors = ["shuriu"]
10
+ spec.email = ["stefan.rotariu@gmail.com"]
11
+
12
+ spec.summary = %q{Small CLI text game that tests your typing speed.}
13
+ spec.homepage = "https://github.com/shuriu/basher"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "bin"
18
+ spec.executables = ["basher"]
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.11"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "minitest", "~> 5.0"
24
+ spec.add_development_dependency "pry"
25
+ spec.add_development_dependency "binding_of_caller"
26
+ spec.add_development_dependency "pry-byebug"
27
+ spec.add_dependency "curtis", "~> 0.1.3"
28
+ spec.add_dependency "artii", "~> 2.1.1"
29
+ end
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env ruby
2
+ require 'optparse'
3
+
4
+ require 'bundler/setup'
5
+ require 'basher'
6
+
7
+ options = {
8
+ debug: false
9
+ }
10
+
11
+ OptionParser.new do |o|
12
+ o.on('-d', '--debug', 'Show small debug bar at the top (kinda useless)') do
13
+ require 'pry'
14
+ require 'binding_of_caller'
15
+
16
+ options[:debug] = true
17
+ end
18
+
19
+ o.on('-v', '--version', 'Print current version') do
20
+ puts Basher::VERSION
21
+ exit
22
+ end
23
+
24
+ o.on('-h', '--help', 'Show this message') do
25
+ puts o
26
+ exit
27
+ end
28
+ end.parse!
29
+
30
+ trap('INT') { exit }
31
+ Basher.start(options)
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "basher"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ require "pry"
10
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,21 @@
1
+ require 'curtis'
2
+ require 'basher/refinements/string'
3
+ require 'basher/version'
4
+ require 'basher/ui'
5
+ require 'basher/game'
6
+
7
+ module Basher
8
+ module_function
9
+
10
+ def start(**options)
11
+ Curtis.show do |screen|
12
+ game = Basher::Game.new(screen, options)
13
+
14
+ Curtis::Input.get do |key|
15
+ result = game.handle(key)
16
+ break if result == :quit
17
+ game.render
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,59 @@
1
+ module Basher
2
+ # Helper class that allows storing movement state inside a collection.
3
+ class Cursor
4
+ # Frozen reference of the word.
5
+ attr_reader :collection
6
+ # Position of the cursor inside #collection. Default is 0.
7
+ attr_reader :index
8
+
9
+ # Returns a Cursor instance for the given collection. The cursor's
10
+ # index is defaulted to 0.
11
+ def initialize(collection)
12
+ @collection = collection
13
+ @index = 0
14
+ end
15
+
16
+ # Gets the item at the cursor.
17
+ def item
18
+ collection[index]
19
+ end
20
+
21
+ # Gets the items before the cursor.
22
+ def previous
23
+ collection[0...index]
24
+ end
25
+
26
+ # Gets remaining items, including item at the cursor.
27
+ def remaining
28
+ collection[index..-1]
29
+ end
30
+
31
+ # Advance the cursor one step.
32
+ def advance!
33
+ @index += 1 unless finished?
34
+ item
35
+ end
36
+
37
+ # Rewind the cursor back to start, and return the item.
38
+ def rewind!
39
+ @index = start
40
+ item
41
+ end
42
+
43
+ # Returns true if the cursor is at the last index,
44
+ # or false otherwise.
45
+ def finished?
46
+ index == finish
47
+ end
48
+
49
+ private
50
+
51
+ def start
52
+ 0
53
+ end
54
+
55
+ def finish
56
+ collection.size
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,50 @@
1
+ module Basher
2
+ # Gives samples of random words as single instances or in batches.
3
+ # TODO: Extract constants to global configuration values.
4
+ # TODO: Allow words_list to be a global config option / value.
5
+ module Dictionary
6
+ module_function
7
+
8
+ # Where is the words list path on the system. It is also assumed
9
+ # that there is only one word per line.
10
+ WORDS_LIST_PATH = '/usr/share/dict/words'.freeze
11
+
12
+ # Filter out words with fewer characters.
13
+ MIN_SIZE = 3
14
+ # Filter out words with more characters.
15
+ MAX_SIZE = 15
16
+
17
+ # Small utility function that preloads the words_list.
18
+ def preload
19
+ !words_list.empty?
20
+ end
21
+
22
+ # Returns a random word from the words file.
23
+ # The size argument must be greater than MIN_SIZE.
24
+ #
25
+ # +size+ - the size of the returned word.
26
+ def random_word(size = MIN_SIZE)
27
+ # Return any word from the list if we supply size: nil
28
+ return words_list.sample unless size
29
+
30
+ grouped_words_list.fetch(size) do
31
+ fail "Size must be in #{MIN_SIZE}..#{MAX_SIZE}"
32
+ end.sample
33
+ end
34
+
35
+ # Group words in the list by size.
36
+ def grouped_words_list
37
+ @grouped_words_list ||= words_list.group_by(&:size)
38
+ end
39
+
40
+ # Returns an array of words, read from a file. Also caches the list.
41
+ def words_list
42
+ @words_list ||= File.open(WORDS_LIST_PATH, 'r') do |file|
43
+ file.each_line.lazy
44
+ .map { |word| word.chomp.downcase }
45
+ .select { |word| word =~ /\A[a-z]{#{MIN_SIZE},#{MAX_SIZE}}\z/ }
46
+ .to_a
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,234 @@
1
+ require 'basher/timer'
2
+ require 'basher/state'
3
+ require 'basher/handler'
4
+ require 'basher/dictionary'
5
+ require 'basher/cursor'
6
+ require 'basher/word'
7
+ require 'basher/level'
8
+
9
+ module Basher
10
+ class Game
11
+ include UI
12
+
13
+ attr_reader :base_view
14
+ attr_reader :timer
15
+ attr_reader :handler
16
+ attr_reader :state
17
+ attr_reader :debug
18
+
19
+ attr_reader :difficulty
20
+ attr_reader :level
21
+ attr_reader :misses
22
+ attr_reader :characters
23
+ attr_reader :words
24
+
25
+ private def setup_default_bindings
26
+ Handler.bind :resize do
27
+ resize_and_reposition
28
+ end
29
+
30
+ Handler.bind 'q' do
31
+ case state.current
32
+ when :paused then back_to_menu
33
+ when :menu then :quit
34
+ when :score then back_to_menu
35
+ end
36
+ end
37
+
38
+ Handler.bind :escape do
39
+ case state.current
40
+ when :in_game
41
+ pause_game
42
+ when :paused
43
+ back_to_game
44
+ when :score
45
+ back_to_menu
46
+ end
47
+ end
48
+
49
+ Handler.bind :enter do
50
+ case state.current
51
+ when :score
52
+ back_to_menu
53
+ end
54
+ end
55
+
56
+ Handler.bind 's' do
57
+ case state.current
58
+ when :menu
59
+ start_game
60
+ end
61
+ end
62
+ end
63
+
64
+ def initialize(base_view, state: :menu, debug: false, bindings: {})
65
+ @debug = debug
66
+
67
+ @base_view = base_view
68
+ base_view.refresh
69
+
70
+ @state = State.new(state)
71
+
72
+ setup_default_bindings
73
+ @handler = Handler.new(bindings)
74
+
75
+ transition_to @state.current
76
+ @timer = Timer.new
77
+ end
78
+
79
+ def handle(input)
80
+ debug_view.last_input = input if debugging?
81
+
82
+ if state.in_game? && handler.letter?(input)
83
+ execute_logic input
84
+ end
85
+
86
+ handler.invoke(input)
87
+ end
88
+
89
+ def execute_logic(char)
90
+ if char == word.char
91
+ next_letter!
92
+ else
93
+ @misses += 1
94
+ level.timer.advance(200)
95
+ end
96
+ end
97
+
98
+ def word
99
+ level.word
100
+ end
101
+
102
+ def next_letter!
103
+ @characters += 1
104
+ word.advance!
105
+
106
+ next_word! if word.finished?
107
+ end
108
+
109
+ def next_word!
110
+ @words += 1
111
+ level.advance!
112
+
113
+ next_level! if level.finished?
114
+ end
115
+
116
+ def next_level!
117
+ @difficulty += 1
118
+ level.finish if level
119
+
120
+ @level = Level.start(difficulty) do
121
+ stop_game
122
+ end
123
+ end
124
+
125
+ def accuracy
126
+ return 0 if total_presses.zero?
127
+ value = (total_presses - misses).to_f / total_presses * 100
128
+ value.round(2)
129
+ end
130
+
131
+ def words_per_minute
132
+ words * 60 / timer.total_elapsed_in_seconds
133
+ end
134
+ alias_method :wpm, :words_per_minute
135
+
136
+ def chars_per_minute
137
+ total_presses * 60 / timer.total_elapsed_in_seconds
138
+ end
139
+ alias_method :cpm, :chars_per_minute
140
+
141
+ def total_presses
142
+ characters + misses
143
+ end
144
+
145
+ def render
146
+ base_view.render
147
+ current_views.each(&:render)
148
+ end
149
+
150
+ def clear
151
+ base_view.clear
152
+ current_views.each(&:clear)
153
+ end
154
+
155
+ def refresh
156
+ base_view.refresh
157
+ current_views.each(&:refresh)
158
+ end
159
+
160
+ def resize_and_reposition
161
+ clear
162
+ views.each(&:will_resize!)
163
+ current_views.each(&:resize_and_reposition)
164
+ render
165
+ end
166
+
167
+ def transition_to(new_state)
168
+ before_transition
169
+ state.transition_to(new_state)
170
+ after_transition
171
+ end
172
+
173
+ private
174
+
175
+ def debugging?
176
+ @debug
177
+ end
178
+
179
+ def playing?
180
+ state.playing? && input.letter?
181
+ end
182
+
183
+ def before_transition
184
+ clear
185
+ refresh
186
+ end
187
+
188
+ def after_transition
189
+ views.each do |view|
190
+ view.resize_and_reposition if view.should_redraw
191
+ end
192
+
193
+ render
194
+ end
195
+
196
+ def pause_game
197
+ timer.stop
198
+ level.timer.stop
199
+ transition_to(:paused)
200
+ end
201
+
202
+ def back_to_menu
203
+ timer.reset
204
+ transition_to(:menu)
205
+ end
206
+
207
+ def back_to_game
208
+ timer.start
209
+ level.timer.start
210
+ transition_to(:in_game)
211
+ end
212
+
213
+ def start_game
214
+ transition_to(:loading)
215
+ Basher::Dictionary.preload
216
+ setup_game
217
+ transition_to(:in_game)
218
+ timer.start
219
+ end
220
+
221
+ def stop_game
222
+ timer.stop
223
+ transition_to(:score)
224
+ end
225
+
226
+ def setup_game
227
+ @difficulty = 0
228
+ @characters = 0
229
+ @misses = 0
230
+ @words = 0
231
+ next_level!
232
+ end
233
+ end
234
+ end