scrabbler 0.1.2

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,54 @@
1
+ ---
2
+ :en:
3
+ :l: 1
4
+ :a: 1
5
+ :x: 8
6
+ :v: 4
7
+ :g: 2
8
+ :n: 1
9
+ :y: 4
10
+ :h: 4
11
+ :u: 1
12
+ :i: 1
13
+ :t: 1
14
+ :w: 4
15
+ :p: 3
16
+ :b: 3
17
+ :c: 3
18
+ :s: 1
19
+ :e: 1
20
+ :k: 5
21
+ :f: 4
22
+ :j: 8
23
+ :m: 3
24
+ :d: 2
25
+ :q: 10
26
+ :r: 1
27
+ :o: 1
28
+ :nl:
29
+ :l: 3
30
+ :a: 1
31
+ :x: 8
32
+ :v: 4
33
+ :g: 3
34
+ :n: 1
35
+ :y: 8
36
+ :h: 4
37
+ :u: 4
38
+ :t: 2
39
+ :i: 1
40
+ :w: 5
41
+ :p: 3
42
+ :b: 3
43
+ :c: 5
44
+ :s: 2
45
+ :e: 1
46
+ :f: 4
47
+ :k: 3
48
+ :j: 4
49
+ :m: 3
50
+ :d: 2
51
+ :q: 10
52
+ :r: 1
53
+ :z: 4
54
+ :o: 1
data/lib/score.rb ADDED
@@ -0,0 +1,22 @@
1
+ # Author:: Peter Maas (pfmmaas [at] gmail [dot] com)
2
+ # Copyright:: Copyright (c) 2008
3
+ # License:: Distributes under the same terms as Ruby
4
+ #
5
+
6
+ class Score
7
+ attr_accessor(:word, :score, :explanation)
8
+
9
+ def initialize(word, score, explanation)
10
+ @word = word
11
+ @score = score
12
+ @explanation = explanation
13
+ end
14
+
15
+ def inspect
16
+ "Scrabble score [word=#{@word}, score=#{@score}]"
17
+ end
18
+
19
+ def <=> other
20
+ other.score <=> @score
21
+ end
22
+ end
data/lib/scrabbler.rb ADDED
@@ -0,0 +1,113 @@
1
+ # Author:: Peter Maas (pfmmaas [at] gmail [dot] com)
2
+ # Copyright:: Copyright (c) 2008
3
+ # License:: Distributes under the same terms as Ruby
4
+
5
+ require 'rubygems'
6
+ require 'ostruct'
7
+ require 'yaml'
8
+
9
+ require File.dirname(__FILE__) +'/array_extensions'
10
+ require File.dirname(__FILE__) +'/score'
11
+ require File.dirname(__FILE__) +'/vocabulary'
12
+
13
+
14
+ # Class for solving scrabble puzzles
15
+ class Scrabbler
16
+ MIN_WORD_SIZE = 3
17
+
18
+ SCRABBLER_DICTIONARIES = {
19
+ :nl => File.dirname(__FILE__) +"/data/nederlands.dict",
20
+ :en => File.dirname(__FILE__) +"/data/en-US.dict"
21
+ }
22
+
23
+ SCORES = YAML::load(File.new(File.dirname(__FILE__) + "/data/scores.yml").read)
24
+ MAX_THREADS = 10;
25
+
26
+ # TODO: rates in constant
27
+
28
+ # initializes the Scrabbler with:
29
+ # - the array if the given object is of type array
30
+ # - the dictonary file specified by the given symbos (:nl, :en)
31
+ def initialize(_dictionary)
32
+ case _dictionary
33
+ when Symbol
34
+ @dictionary = Vocabulary.new(File.read(SCRABBLER_DICTIONARIES[_dictionary]))
35
+ @dictionary.info = _dictionary
36
+ when Array
37
+ @dictionary = Vocabulary.new(_dictionary)
38
+ @dictionary.info = "based on supplied array"
39
+ end
40
+ end
41
+
42
+ # find possible solutions of the given array of letters
43
+ def scrabble(letters, min_score = 10, rating = :nl, wildcard = '$')
44
+ raise "Wildcard can not be a word character" if wildcard =~ /[\w]/
45
+
46
+ letters = letters.scan(/[a-z#{wildcard}]/)
47
+ num_wildcards = letters.select{|c| c == wildcard}.length # count the number of wildcards
48
+
49
+ # create a subset based on the word letters
50
+ dictonary_subset = @dictionary.calculate_subset(letters, wildcard, MIN_WORD_SIZE)
51
+
52
+ scores, threads = [], []
53
+ chunks = dictonary_subset.chunk(@dictionary.words.length / (MAX_THREADS - 1) + 1)
54
+
55
+ chunks.each do |chunk|
56
+ threads << Thread.new(chunk) do |c|
57
+ chunk_scores = []
58
+ chunk.each do |word|
59
+ score = calculate_score(letters, word, num_wildcards, rating)
60
+ chunk_scores << score if score && score.score > min_score
61
+ end
62
+ chunk_scores
63
+ end
64
+ end
65
+
66
+ threads.each {|t| scores = scores + t.value} # gather values from all threads
67
+
68
+ scores.sort
69
+ end
70
+
71
+ # calculate the score of the test_word. Doesn't return anything
72
+ # if it is impossible to create the word from the given letters
73
+ def calculate_score(letters, test_word, num_wild = 0, rating = :nl)
74
+ return if letters.length < test_word.length
75
+
76
+ letters = letters.dup # make a copy, we'll use the array desctructively
77
+ score_sum, score_explanation = 0, []
78
+
79
+ test_word.scan(/[a-z]/).each do |c|
80
+ if letters.include? c
81
+ letters.remove_first{|v| v == c} # we've used this letter, remove it
82
+ curr_rate = SCORES[rating][c.to_sym];
83
+ score_sum = score_sum + curr_rate
84
+ score_explanation << "#{c} (#{curr_rate})"
85
+ elsif num_wild > 0 # see if we've got any wildcards left
86
+ num_wild = num_wild - 1
87
+ score_explanation << "#{c} (wildcard)"
88
+ else
89
+ return
90
+ end
91
+ end
92
+
93
+ # return the result
94
+ Score.new(test_word, score_sum , score_explanation)
95
+ end
96
+
97
+ def inspect
98
+ "scrabbler, dictionary: #{@dictionary.info}"
99
+ end
100
+
101
+ def self.def_singletons(*args)
102
+ args.each do |arg|
103
+ module_eval <<-EOS
104
+ def self.get_#{arg}
105
+ @@instance_#{arg} ||= Scrabbler.new(:#{arg})
106
+ end
107
+ EOS
108
+ end
109
+ end
110
+
111
+ def_singletons *SCRABBLER_DICTIONARIES.keys
112
+
113
+ end
data/lib/vocabulary.rb ADDED
@@ -0,0 +1,51 @@
1
+ # Author:: Peter Maas (pfmmaas [at] gmail [dot] com)
2
+ # Copyright:: Copyright (c) 2008
3
+ # License:: Distributes under the same terms as Ruby
4
+
5
+ # Vocabulary is a simple dictionary with options for creating subsets
6
+ class Vocabulary
7
+ attr_accessor(:words, :info, :index)
8
+
9
+ # removes non-word characters
10
+ SANITIZE_FILTER = proc{|v| v.chomp.downcase.gsub(/\W/,'')}
11
+
12
+ def initialize(words)
13
+ @words = words.map(&SANITIZE_FILTER)
14
+ build_index
15
+ end
16
+
17
+ def build_index
18
+ @index = Hash.new { |hash, key| hash[key] = [] }
19
+ @words.each do |w|
20
+ w.scan(/[a-z]/).uniq.each do |c|
21
+ @index[c.to_sym] << w
22
+ end
23
+ end
24
+ end
25
+
26
+ def calculate_subset(letters, wildcard, min_word_size = 3)
27
+ # create a subset based on the word letters
28
+ lf = length_filter(min_word_size..letters.length)
29
+
30
+ if letters.include? wildcard
31
+ subset = @words.select(&lf)
32
+ else
33
+ subset = []
34
+ letters.uniq.each do |c|
35
+ subset = subset + index[c.to_sym]
36
+ end
37
+ subset = subset.select(&lf)
38
+ subset = subset.uniq.grep(create_pattern(letters, min_word_size))
39
+ end
40
+
41
+ subset
42
+ end
43
+
44
+ def length_filter(range)
45
+ proc{|s| range.include? s.length}
46
+ end
47
+
48
+ def create_pattern(letters, min_word_size = 3)
49
+ /^[#{letters.join}]{#{min_word_size},#{letters.length}}$/
50
+ end
51
+ end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestScrabbler < Test::Unit::TestCase
4
+
5
+ def test_remove_first
6
+ r = %w(the quick brown fox jumps over the lazy dog)
7
+ r.remove_first{|i| i == 'the'}
8
+ assert_equal(%w(quick brown fox jumps over the lazy dog), r)
9
+
10
+ r.remove_first{|i| i == 'the'}
11
+ assert_equal(%w(quick brown fox jumps over lazy dog), r)
12
+
13
+ r.remove_first{|i| i == 'the'}
14
+ assert_equal(%w(quick brown fox jumps over lazy dog), r)
15
+ end
16
+
17
+ def test_uses_input_block
18
+ assert_raise BlockCalled do
19
+ r = %w(the quick brown fox jumps over the lazy dog)
20
+ r.remove_first{|i| raise BlockCalled}
21
+ end
22
+ end
23
+
24
+ def test_chunk
25
+ r = %w(the quick brown fox jumps over the lazy dog)
26
+ assert_equal([%w(the quick brown),%w(fox jumps over),%w(the lazy dog)], r.chunk(3))
27
+
28
+ r = %w(the quick brown fox jumps over the lazy dog but didn't jump over the fast bunny)
29
+ assert_equal([%w(the quick brown),%w(fox jumps over),%w(the lazy dog), %w(but didn't jump), %w(over the fast), %w(bunny)], r.chunk(3))
30
+ end
31
+ end
@@ -0,0 +1,6 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/scrabbler.rb'
3
+
4
+ # class to use in
5
+ class BlockCalled < RuntimeError
6
+ end
@@ -0,0 +1,44 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestScrabbler < Test::Unit::TestCase
4
+
5
+ def setup
6
+ dictionary = %w(the quick brown fox jumps over the lazy dog but not over the quicker bunny)
7
+ @scrabbler = Scrabbler.new(dictionary)
8
+ end
9
+
10
+ def test_run_scrabble
11
+ results = @scrabbler.scrabble("q,u,i,c,k", 19, :en)
12
+ assert_equal(1, results.length)
13
+ assert_equal(20, results[0].score)
14
+ end
15
+
16
+ def test_illegal_wildcard
17
+ assert_raise RuntimeError do
18
+ results = @scrabbler.scrabble("q,u,i,c,k", 19, :en,'a') # wildcard can not be a word character
19
+ end
20
+ end
21
+
22
+ def test_run_scrabble_with_one_wildcard
23
+ results = @scrabbler.scrabble("q,u,i,$,k", 15, :en)
24
+ assert_equal(1, results.length)
25
+ assert_equal(17, results[0].score) # score should be lower, wildcard has no score
26
+ end
27
+
28
+ def test_run_scrabble_with_two_wildcards
29
+ results = @scrabbler.scrabble("q,u,i,c,k,$,$", 19, :en)
30
+ assert_equal(2, results.length)
31
+ assert_equal(results[0].score, results[1].score)
32
+ end
33
+
34
+ def test_results_should_be_ordered_by_score
35
+ results = @scrabbler.scrabble("q,u,i,c,k,e,r", 19, :en)
36
+ assert_equal(2, results.length)
37
+ assert(results[0].score > results[1].score, "scores are not ordered correctly")
38
+ end
39
+
40
+ #def test_singleton_methods
41
+ # assert Scrabbler.get_nl.equal?(Scrabbler.get_nl)
42
+ # assert Scrabbler.get_en.equal?(Scrabbler.get_en)
43
+ #end
44
+ end
@@ -0,0 +1,19 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestVocabulary < Test::Unit::TestCase
4
+
5
+ def setup
6
+ dictionary = %w(the quick brown fox jumps over the lazy dog but not over the quicker bunny)
7
+ @voc = Vocabulary.new(dictionary)
8
+ end
9
+
10
+ def test_create_pattern
11
+ ta = ('a'..'j').to_a
12
+ assert_equal(/^[#{ta.join()}]{#{Scrabbler::MIN_WORD_SIZE},#{ta.length}}$/, @voc.create_pattern(ta))
13
+ end
14
+
15
+ def test_calculate_subset
16
+ tl = %w(q u i c k)
17
+ assert_equal(['quick'], @voc.calculate_subset(tl, '$'))
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.4
3
+ specification_version: 1
4
+ name: scrabbler
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.2
7
+ date: 2008-01-30 00:00:00 +01:00
8
+ summary: A bruteforce word finder for scrabble.
9
+ require_paths:
10
+ - lib
11
+ email: pfmmaas @nospam@ gmail -dot- com
12
+ homepage:
13
+ rubyforge_project:
14
+ description:
15
+ autorequire: scrabbler
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors:
30
+ - Peter Maas
31
+ files:
32
+ - lib/array_extensions.rb
33
+ - lib/score.rb
34
+ - lib/scrabbler.rb
35
+ - lib/vocabulary.rb
36
+ - lib/data/en-US.dict
37
+ - lib/data/nederlands.dict
38
+ - lib/data/scores.yml
39
+ - bin/scrabblit
40
+ - README
41
+ test_files:
42
+ - test/test_array_extensions.rb
43
+ - test/test_helper.rb
44
+ - test/test_scrabbler.rb
45
+ - test/test_vocabulary.rb
46
+ rdoc_options: []
47
+
48
+ extra_rdoc_files:
49
+ - README
50
+ executables:
51
+ - scrabblit
52
+ extensions: []
53
+
54
+ requirements: []
55
+
56
+ dependencies: []
57
+