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.
- data/.gemtest +0 -0
- data/Gemfile +6 -0
- data/Guardfile +9 -0
- data/LICENSE +16 -0
- data/README.md +76 -0
- data/assets/words.txt +24001 -0
- data/bin/scrabble-solver +68 -0
- data/lib/scrabble-solver.rb +3 -0
- data/lib/scrabble-solver/solver.rb +110 -0
- data/spec/scrabble-solver/solver_spec.rb +181 -0
- data/spec/spec_helper.rb +1 -0
- metadata +58 -0
data/bin/scrabble-solver
ADDED
@@ -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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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: []
|