scrabble-solver 0.1

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.
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+ require File.dirname(__FILE__) + '/../lib/scrabble-solver'
5
+
6
+ options = Hash.new(nil)
7
+
8
+ # Use the optparse library to populate the options hash.
9
+ OptionParser.new do |o|
10
+ o.banner = 'Usage: scrabble-solver tiles [options]'
11
+
12
+ # Add the README contents to the --help flag.
13
+ o.separator "\n" + File.read(File.dirname(__FILE__) + '/../README.md') + "\n"
14
+
15
+ o.separator "Optional arguments:"
16
+
17
+ o.on '-h', '--help', 'Display this message' do
18
+ puts o
19
+ exit
20
+ end
21
+
22
+ o.on '--longer-than LENGH',
23
+ 'Only return words longer than LENGTH.' do |length|
24
+ options[:longer_than] = length
25
+ end
26
+
27
+ o.on '--shorter-than LENGH',
28
+ 'Only return words shorter than LENGTH.' do |length|
29
+ options[:shorter_than] = length
30
+ end
31
+
32
+ o.on '--starts-with PREFIX',
33
+ 'Only return words that start with PREFIX.' do |prefix|
34
+ options[:starts_with] = prefix
35
+ end
36
+
37
+ o.on '--ends-with SUFFIX',
38
+ 'Only return words that end with SUFFIX.' do |suffix|
39
+ options[:ends_with] = suffix
40
+ end
41
+
42
+ o.on '--contains PART',
43
+ 'Only return words that contain PART. Must be combined with --at.' do |part|
44
+ options[:contains] = part
45
+ end
46
+
47
+ o.on '--at INDEX',
48
+ 'The index to search for --contains at.' do |index|
49
+ options[:at] = index
50
+ end
51
+
52
+ o.on '--word-file FILE',
53
+ 'The word file to use. Defaults to a preset list of words.' do |file|
54
+ options[:word_file] = file
55
+ end
56
+ end.parse!
57
+
58
+ # Pull the tiles from the first argument.
59
+ letters = ARGV[0]
60
+
61
+ # Exit the program if no first argument was supplied.
62
+ if letters.nil?
63
+ puts "You need to supply letters to work with as the first argument."
64
+ exit
65
+ end
66
+
67
+ # If all is well, solve :)
68
+ puts Scrabble::Solver.words_for letters, options
@@ -0,0 +1,3 @@
1
+ Dir[File.dirname(__FILE__) + "/scrabble-solver/*.rb"].each do |file|
2
+ require file
3
+ end
@@ -0,0 +1,110 @@
1
+ module Scrabble
2
+ module Solver
3
+ # The name of the file to use as a word dictionary. This file can be any
4
+ # file that contains a list of words, one word per line.
5
+ @word_file_name = File.dirname(__FILE__) + "/../../assets/words.txt"
6
+
7
+ # Set a new word file. Exit the program if the file does not exist.
8
+ #
9
+ # If the file does exist, the cached word list is cleared out and the
10
+ # next time the word list is requested, the new list from the new file
11
+ # is returned and subsequently cached.
12
+ def self.word_file_name= file_name
13
+ if File.exists? file_name
14
+ # Reset the word list
15
+ @word_list = nil
16
+ @word_file_name = file_name
17
+ else
18
+ puts "The file name #{file_name} is not valid."
19
+ exit
20
+ end
21
+ end
22
+
23
+ # Reads in the words.txt file and returns an array containing all of the words
24
+ # in that file.
25
+ #
26
+ # Example:
27
+ #
28
+ # Scrabble::Solver.word_list.each do |word|
29
+ # puts word
30
+ # end
31
+ def self.word_list
32
+ @word_list ||= File.open(@word_file_name) do |file|
33
+ file.readlines.map do |line|
34
+ line.chomp
35
+ end.delete_if do |line|
36
+ line.length < 2
37
+ end
38
+ end
39
+
40
+ @word_list.clone
41
+ end
42
+
43
+ # Gets an array of words that would fit the current board of Scrabble
44
+ # tiles.
45
+ #
46
+ # Example:
47
+ #
48
+ # Scrabble::Solver.words_for "there"
49
+ # # => An array of words that the tiles t, h, e, r, e could make.
50
+ #
51
+ # Scrabble::Solver.words_for "there?"
52
+ # # => An array of words that the tiles t, h, e, r, e plus a blank tile
53
+ # # could make.
54
+ def self.words_for letters, options = Hash.new(nil)
55
+ letters = letters.downcase.split(//)
56
+ unknowns = letters.count "?"
57
+ letters.delete "?"
58
+
59
+ # Set a new word file if the option has been specified
60
+ if options[:word_file]
61
+ self.word_file_name = options[:word_file]
62
+ end
63
+
64
+ words = word_list.keep_if do |word|
65
+ # Split the word into its letters.
66
+ word = word.split(//)
67
+
68
+ # Strip the letters that are in our hand from the word.
69
+ letters.each do |letter|
70
+ unless word.index(letter).nil?
71
+ word.delete_at word.index(letter)
72
+ end
73
+ end
74
+
75
+ # Only return the word if the remaining letters is equal to the number
76
+ # of unknowns.
77
+ word.length == unknowns
78
+ end
79
+
80
+ # Filter only words that start with a specific sequence.
81
+ if options[:starts_with]
82
+ words.keep_if { |word| word.start_with? options[:starts_with] }
83
+ end
84
+
85
+ # Filter words that only end in a certain sequence.
86
+ if options[:ends_with]
87
+ words.keep_if { |word| word.end_with? options[:ends_with] }
88
+ end
89
+
90
+ # Fitler only words shorter than a given amount.
91
+ if options[:shorter_than]
92
+ words.keep_if { |word| word.length < options[:shorter_than].to_i }
93
+ end
94
+
95
+ # Filter words only longer than a given amount.
96
+ if options[:longer_than]
97
+ words.keep_if { |word| word.length > options[:longer_than].to_i }
98
+ end
99
+
100
+ # Filter words that contain a specific sequence at a given 1-based index.
101
+ if options[:contains] and options[:at]
102
+ words.keep_if do |word|
103
+ word[options[:at].to_i - 1, options[:contains].length] == options[:contains]
104
+ end
105
+ end
106
+
107
+ return words
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,181 @@
1
+ require File.dirname(__FILE__) + "/../spec_helper"
2
+
3
+ module Scrabble
4
+ describe Solver do
5
+ # Specify the name of the test word file.
6
+ let(:test_word_file) do
7
+ File.dirname(__FILE__) + "/../assets/test_word_list.txt"
8
+ end
9
+
10
+ # Path to the solver executable
11
+ let (:executable) do
12
+ File.dirname(__FILE__) + "/../../bin/scrabble-solver"
13
+ end
14
+
15
+ context "Direct" do
16
+ it "should return a list of words that can be made" do
17
+ words = Solver.words_for "there"
18
+ words.should include "three", "there", "ether", "the", "thee", "tee"
19
+ end
20
+
21
+ it "should be able to use blank tiles with ?" do
22
+ words = Solver.words_for "g?me"
23
+ words.should include "game", "egg", "peg", "gel", "germ", "get", "go", "gum"
24
+ end
25
+
26
+ it "should be able to use a start_with argument successfully" do
27
+ words = Solver.words_for "geg", starts_with: "eg"
28
+ words.should include "egg"
29
+ end
30
+
31
+ it "should be able to use an ends_with argument successfully" do
32
+ words = Solver.words_for "pngi", ends_with: "ing"
33
+ words.should include "ping"
34
+ end
35
+
36
+ it "should be able to use a combination of starts_with and ends_with" do
37
+ words = Solver.words_for "crcak", starts_with: "cr", ends_with: "k"
38
+ words.should include "crack"
39
+ end
40
+
41
+ it "should be able to filter words by length" do
42
+ words = Solver.words_for "diegtlkwj", longer_than: "4"
43
+ words.each do |word|
44
+ word.length.should be > 4
45
+ end
46
+
47
+ # Ensure that some words were actually checked in the above loop.
48
+ words.length.should be > 0, "No words scanned."
49
+ end
50
+
51
+ it "should be able to filter by length, less than" do
52
+ words = Solver.words_for "diegtlkwj", shorter_than: "4"
53
+ words.each do |word|
54
+ word.length.should be < 4
55
+ end
56
+
57
+ # Ensure that some words were actually checked in the above loop.
58
+ words.length.should be > 0, "No words scanned."
59
+ end
60
+
61
+ it "should be able to filter by length both less than and greater than" do
62
+ words = Solver.words_for "diegtlkwj", shorter_than: "6", longer_than: "4"
63
+ words.each do |word|
64
+ word.length.should be > 4 and word.length.should be < 6
65
+ end
66
+
67
+ # Ensure that some words were actually checked in the above loop.
68
+ words.length.should be > 0, "No words scanned."
69
+ end
70
+
71
+ it "should be able to return only words that have a specific letter at " +
72
+ "a given index" do
73
+ words = Solver.words_for "diegti?wj", contains: "i", at: "2"
74
+ words.each do |word|
75
+ word[1].should == "i"
76
+ end
77
+
78
+ # Ensure that some words were actually checked in the above loop.
79
+ words.length.should be > 0, "No words scanned."
80
+ end
81
+
82
+ it "should be able to return only words that have a specific middle part" do
83
+ words = Solver.words_for "diegti?wj", contains: "it", at: "2"
84
+ words.each do |word|
85
+ word[1, 2].should == "it"
86
+ end
87
+
88
+ # Ensure that some words were actually checked in the above loop.
89
+ words.length.should be > 0, "No words scanned."
90
+ end
91
+
92
+ it "should be able to take a new word file if specified" do
93
+ words = Solver.words_for "????", word_file: test_word_file
94
+ words.should be_empty
95
+ end
96
+ end
97
+
98
+ context "Command-line" do
99
+ it "should return a list of words that can be made" do
100
+ words = `#{executable} tehre`.split(/\n/)
101
+ words.should include "three", "there", "ether", "the", "thee", "tee"
102
+ end
103
+
104
+ it "should be able to use blank tiles with ?" do
105
+ words = `#{executable} g?me`.split(/\n/)
106
+ words.should include "game", "egg", "peg", "gel", "germ", "get", "go", "gum"
107
+ end
108
+
109
+ it "should be able to use a start_with argument successfully" do
110
+ words = `#{executable} geg --starts-with eg`.split(/\n/)
111
+ words.should include "egg"
112
+ end
113
+
114
+ it "should be able to use an ends_with argument successfully" do
115
+ words = `#{executable} pngi --ends-with ing`.split(/\n/)
116
+ words.should include "ping"
117
+ end
118
+
119
+ it "should be able to use a combination of starts_with and ends_with" do
120
+ words = `#{executable} crcak --starts-with cr --ends-with k`.split(/\n/)
121
+ words.should include "crack"
122
+ end
123
+
124
+ it "should be able to filter words by length" do
125
+ words = `#{executable} diegtlkwj --longer-than 4`.split(/\n/)
126
+ words.each do |word|
127
+ word.length.should be > 4
128
+ end
129
+
130
+ # Ensure that some words were actually checked in the above loop.
131
+ words.length.should be > 0, "No words scanned."
132
+ end
133
+
134
+ it "should be able to filter by length, less than" do
135
+ words = `#{executable} diegtlkwj --shorter-than 4`.split(/\n/)
136
+ words.each do |word|
137
+ word.length.should be < 4
138
+ end
139
+
140
+ # Ensure that some words were actually checked in the above loop.
141
+ words.length.should be > 0, "No words scanned."
142
+ end
143
+
144
+ it "should be able to filter by length both less than and greater than" do
145
+ words = `#{executable} diegtlkwj --shorter-than 6 --longer-than 4`.split(/\n/)
146
+ words.each do |word|
147
+ word.length.should be > 4 and word.length.should be < 6
148
+ end
149
+
150
+ # Ensure that some words were actually checked in the above loop.
151
+ words.length.should be > 0, "No words scanned."
152
+ end
153
+
154
+ it "should be able to return only words that have a specific letter at " +
155
+ "a given index" do
156
+ words = `#{executable} diegtlkwj --contains i --at 2`.split(/\n/)
157
+ words.each do |word|
158
+ word[1].should == "i"
159
+ end
160
+
161
+ # Ensure that some words were actually checked in the above loop.
162
+ words.length.should be > 0, "No words scanned."
163
+ end
164
+
165
+ it "should be able to return only words that have a specific middle part" do
166
+ words = `#{executable} diegti?wj --contains it --at 2`.split(/\n/)
167
+ words.each do |word|
168
+ word[1, 2].should == "it"
169
+ end
170
+
171
+ # Ensure that some words were actually checked in the above loop.
172
+ words.length.should be > 0, "No words scanned."
173
+ end
174
+
175
+ it "should be able to take a new word file if specified" do
176
+ words = `#{executable} ???? --word-file #{test_word_file}`.split(/\n/)
177
+ words.should be_empty
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/../lib/scrabble-solver"
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: scrabble-solver
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sam Rose
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-08 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Want to win at Scrabble while staying inside your comfy command line
15
+ interface? No problem! Scrabble solver lets you do that.
16
+ email: samwho@lbak.co.uk
17
+ executables:
18
+ - scrabble-solver
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - bin/scrabble-solver
23
+ - lib/scrabble-solver.rb
24
+ - lib/scrabble-solver/solver.rb
25
+ - spec/scrabble-solver/solver_spec.rb
26
+ - spec/spec_helper.rb
27
+ - assets/words.txt
28
+ - .gemtest
29
+ - README.md
30
+ - LICENSE
31
+ - Gemfile
32
+ - Guardfile
33
+ homepage: http://github.com/samwho/scrabble-solver
34
+ licenses:
35
+ - GPL-2
36
+ post_install_message:
37
+ rdoc_options: []
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 1.9.2
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ requirements: []
53
+ rubyforge_project:
54
+ rubygems_version: 1.8.6
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: A command line Scrabble solving utility.
58
+ test_files: []