boggle_solver 0.0.5 → 0.1.0

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.
@@ -1,12 +1,9 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "boggle_solver"
3
3
  s.summary = "Find all words in a letter matrix"
4
- s.description = "Ruby program that employs graph theory to solve a word matrix puzzle (Boggle).
5
-
6
- # Introduction
7
- Boggle Master consists of a 5x5 grid of letters. The goal of the game is to make as many words as possible from this grid by connecting neighboring letters. To generate a random puzzle the game uses 25 dice with letters on each side. The dice are shaken and each fall into a slot in the grid."
4
+ s.description = "Ruby program that employs graph theory to solve a word matrix puzzle (Boggle)."
8
5
  s.requirements = "[ 'An installed dictionary (most Unix systems have one)']"
9
- s.version = "0.0.5"
6
+ s.version = "0.1.0"
10
7
  s.author = "Casey Robinson"
11
8
  s.email = "kc@rampantmonkey.com"
12
9
  s.homepage = "http://rampantmonkey.com"
@@ -143,6 +143,10 @@ module BoggleSolver
143
143
  Thread.current[:visited][u] = nil
144
144
  end
145
145
 
146
+ def score
147
+ @words.inject(0) { |points, w| points + w.length}
148
+ end
149
+
146
150
  end
147
151
 
148
152
  end
@@ -12,6 +12,7 @@ module BoggleSolver
12
12
  board = Board.new(@options.mode,@options.input,@options.dictionary)
13
13
  board.find_words
14
14
  p board.words
15
+ p board.score
15
16
  end
16
17
 
17
18
  end
@@ -0,0 +1,59 @@
1
+ Ruby program that employs graph theory to solve a word matrix puzzle (Boggle).
2
+
3
+ # Introduction
4
+ Boggle Master consists of a 5x5 grid of letters. The goal of the game is to make as many words as possible from this grid by connecting neighboring letters. To generate a random puzzle the game uses 25 dice with letters on each side. The dice are shaken and each fall into a slot in the grid.
5
+
6
+ # Example Grid
7
+
8
+ Below is an example grid. One possible word in this graph is `wear`. Arrows are added for convenience.
9
+
10
+ J O X T Y
11
+
12
+ A C D F P
13
+
14
+ Y K B E O
15
+
16
+ A M G P W
17
+ |
18
+ D S R<--A<--E
19
+
20
+ The goal of this project is to find all possible words. Mostly to serve as a benchmark with which to compare my performance.
21
+
22
+
23
+ # Coordinate System
24
+ Below are the indicies used to represent the dice in the board:
25
+
26
+ 0 1 2 3 4
27
+ 5 6 7 8 9
28
+ 10 11 12 13 14
29
+ 15 16 17 18 19
30
+ 20 21 22 23 24
31
+
32
+ # Connection Matrix
33
+ Adjacency matrix representing the connections in Boggle:
34
+
35
+ 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
36
+ 1 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
37
+ 0 1 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
38
+ 0 0 1 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
39
+ 0 0 0 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
40
+ 1 1 0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0
41
+ 1 1 1 0 0 1 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0
42
+ 0 1 1 1 0 0 1 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0 0
43
+ 0 0 1 1 1 0 0 1 0 1 0 0 1 1 1 0 0 0 0 0 0 0 0 0 0
44
+ 0 0 0 1 1 0 0 0 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0
45
+ 0 0 0 0 0 1 1 0 0 0 0 1 0 0 0 1 1 0 0 0 0 0 0 0 0
46
+ 0 0 0 0 0 1 1 1 0 0 1 0 1 0 0 1 1 1 0 0 0 0 0 0 0
47
+ 0 0 0 0 0 0 1 1 1 0 0 1 0 1 0 0 1 1 1 0 0 0 0 0 0
48
+ 0 0 0 0 0 0 0 1 1 1 0 0 1 0 1 0 0 1 1 1 0 0 0 0 0
49
+ 0 0 0 0 0 0 0 0 1 1 0 0 0 1 0 0 0 0 1 1 0 0 0 0 0
50
+ 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 0 0 1 1 0 0 0
51
+ 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 1 0 0 1 1 1 0 0
52
+ 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 1 0 0 1 1 1 0
53
+ 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 1 0 0 1 1 1
54
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1 0 0 0 0 1 1
55
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 0 0
56
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 1 0 0
57
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 1 0
58
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 0 1 0 1
59
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 1 0
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'rubygems/package_task'
3
+ spec = eval(File.read('boggle_solver.gemspec'))
4
+ Gem::PackageTask.new(spec) do |pkg| end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'boggle_solver/runner'
4
+
5
+ runner = BoggleSolver::Runner.new(ARGV)
6
+ runner.run
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "boggle_solver"
3
+ s.summary = "Find all words in a letter matrix"
4
+ s.description = "Ruby program that employs graph theory to solve a word matrix puzzle (Boggle).
5
+
6
+ # Introduction
7
+ Boggle Master consists of a 5x5 grid of letters. The goal of the game is to make as many words as possible from this grid by connecting neighboring letters. To generate a random puzzle the game uses 25 dice with letters on each side. The dice are shaken and each fall into a slot in the grid."
8
+ s.requirements = "[ 'An installed dictionary (most Unix systems have one)']"
9
+ s.version = "0.0.5"
10
+ s.author = "Casey Robinson"
11
+ s.email = "kc@rampantmonkey.com"
12
+ s.homepage = "http://rampantmonkey.com"
13
+ s.platform = Gem::Platform::RUBY
14
+ s.required_ruby_version = '>=1.9'
15
+ s.files = Dir['**/**']
16
+ s.executables = [ 'boggle_solver' ]
17
+ s.test_files = Dir["test/test*.rb"]
18
+ s.has_rdoc = false
19
+ end
@@ -0,0 +1,52 @@
1
+ module BoggleSolver
2
+
3
+ class AdjacencyMatrix
4
+ attr_reader :visited, :finished
5
+
6
+ def initialize( n = 2 )
7
+ @m = Array.new(n, 0)
8
+ @m.map!{Array.new(n, 0)}
9
+ @n = n
10
+ end
11
+
12
+ def to_s
13
+ s = ""
14
+ @m.each do |row|
15
+ row.each {|e| s += e.to_s + " "}
16
+ s += "\n"
17
+ end
18
+ s.chop
19
+ end
20
+
21
+ def add_directed_edge(i, j, w=1)
22
+ @m[i][j] = w unless i == j
23
+ end
24
+
25
+ def add_edge(i, j, w=1)
26
+ add_directed_edge(i, j, w)
27
+ add_directed_edge(j, i, w)
28
+ end
29
+
30
+ def adjacent(u)
31
+ a = Array.new
32
+ @m[u].each_with_index {|v,i| a.push i unless v == 0 }
33
+ a
34
+ end
35
+
36
+ def depth_first_search
37
+ @visited = Array.new(@n, nil)
38
+ @finished = Array.new(@n, nil)
39
+ @visited.each_with_index do |visit_status, u|
40
+ depth_first_search_visit(u) if visit_status.nil?
41
+ end
42
+ end
43
+
44
+ def depth_first_search_visit(u, *)
45
+ @visited[u] = 1
46
+ adjacent(u).each { |v| depth_first_search_visit(v) if @visited[v].nil?}
47
+ @finished[u] = 1
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,148 @@
1
+ require_relative 'adjacency_matrix'
2
+ require 'thread'
3
+
4
+ module BoggleSolver
5
+
6
+ class Board < AdjacencyMatrix
7
+ attr_reader :words
8
+
9
+ def initialize(mode=:default, input, dictionary_file)
10
+ super(25)
11
+ connect
12
+ @board = []
13
+ if mode == :random
14
+ @prng = Random.new
15
+ generate_random_board
16
+ elsif mode == :given
17
+ @board = input.split(//)
18
+ else
19
+ generate_board
20
+ end
21
+ calculate_board_hash
22
+ puts self
23
+ @words = []
24
+ set_dictionary dictionary_file
25
+ load_dictionary
26
+ end
27
+
28
+ def set_dictionary file
29
+ @dictionary_file = file
30
+ end
31
+
32
+ def connect
33
+ j=0;
34
+ while j<25 do
35
+ add_edge(j, j-6) unless j < 6 or j % 5 == 0
36
+ add_edge(j, j-5) unless j < 5
37
+ add_edge(j, j-4) unless j < 4 or j % 5 == 4
38
+ add_edge(j, j-1) unless j % 5 == 0
39
+ add_edge(j, j+1) unless j % 5 == 4
40
+ add_edge(j, j+4) unless j > 19 or j % 5 == 0
41
+ add_edge(j, j+5) unless j > 19
42
+ add_edge(j, j+6) unless j > 18 or j % 5 == 4
43
+ j += 1
44
+ end
45
+ end
46
+
47
+ def generate_board
48
+ @board = ["a", "r", "t", "a", "c", "c", "e", "a", "r", "d", "f", "e", "w", "o", "o", "n", "p", "l", "m", "i", "c", "r", "u", "b", "l"]
49
+ end
50
+
51
+ def load_board_from_string s
52
+ @board = s.split(//) if s.length == 25
53
+ end
54
+
55
+ def generate_random_board
56
+ @board = Array.new(25)
57
+ @board.map!{|item| (65.+rand(25)).chr.downcase!}
58
+ end
59
+
60
+ def calculate_board_hash
61
+ @board_hash = Hash.new(0)
62
+ @board.each {|letter| @board_hash[letter] += 1}
63
+ end
64
+
65
+ def load_dictionary
66
+ @dictionary = Array.new
67
+
68
+ File.foreach(@dictionary_file) do |line|
69
+ next_word = line.chomp
70
+ @dictionary.push next_word if possible? next_word
71
+ end
72
+ end
73
+
74
+ def possible? word
75
+ word_hash = Hash.new(0)
76
+ result = true
77
+ word.each_char {|letter| word_hash[letter] += 1}
78
+ word_hash.each do |letter, count|
79
+ result &&= count <= @board_hash[letter]
80
+ end
81
+ result
82
+ end
83
+
84
+ def to_s
85
+ s = ""
86
+ @board.each_with_index do |letter, index|
87
+ s += letter.to_s + " "
88
+ s += "\n" if index % 5 == 4
89
+ end
90
+ s.chomp
91
+ end
92
+
93
+ def in_dictionary? s
94
+ @dictionary.index s
95
+ end
96
+
97
+ def part_in_dictionary? s
98
+ s = "\\A" + s + "..*"
99
+ regex = Regexp.new(s)
100
+ !(@dictionary.grep regex).empty?
101
+ end
102
+
103
+ def find_words
104
+ threads = []
105
+ @board.each_with_index do |letter, index|
106
+ threads << Thread.new { depth_first_search(index) }
107
+ end
108
+
109
+ threads.each do |t|
110
+ t.join
111
+ @words.concat t[:words]
112
+ end
113
+
114
+ @words.uniq!
115
+ @words.sort_by!{|item| item.length}
116
+ @words = @words.drop_while{|w| w.length < 3}
117
+ end
118
+
119
+ def depth_first_search(offset=0)
120
+ Thread.current[:visited] = Array.new(@n, nil)
121
+ Thread.current[:finished] = Array.new(@n, nil)
122
+ Thread.current[:words] = []
123
+ Thread.current[:visited][offset..-1].each_with_index do |visit_status, u|
124
+ u += offset
125
+ search_string = @board[u]
126
+ depth_first_search_visit(u, search_string) if Thread.current[:finished][u].nil?
127
+ end
128
+ end
129
+
130
+ def depth_first_search_visit(u, search_string)
131
+ Thread.current[:visited][u] = 1
132
+ Thread.current[:words] << search_string if in_dictionary? search_string
133
+ adjacent(u).each do |v|
134
+ if Thread.current[:visited][v].nil?
135
+ s = search_string + @board[v]
136
+ Thread.current[:words].push s if in_dictionary? s
137
+ if part_in_dictionary? s
138
+ depth_first_search_visit(v, s)
139
+ end
140
+ end
141
+ end
142
+ Thread.current[:finished][u] = 1
143
+ Thread.current[:visited][u] = nil
144
+ end
145
+
146
+ end
147
+
148
+ end
@@ -0,0 +1,42 @@
1
+ require 'optparse'
2
+
3
+ module BoggleSolver
4
+ class Options
5
+
6
+ DEFAULT_DICTIONARY = "/usr/share/dict/words"
7
+
8
+ attr_reader :dictionary, :mode, :input
9
+
10
+ def initialize(argv)
11
+ @dictionary = DEFAULT_DICTIONARY
12
+ @mode = :default
13
+ parse(argv)
14
+ @input = argv[0]
15
+ @mode = :given unless argv.empty?
16
+ end
17
+
18
+ def parse(argv)
19
+ OptionParser.new do |opts|
20
+ opts.banner = "Usage: boggle_solver [ options ] "
21
+ opts.on("-d", "--dict path", String, "Path to dictionary") do |dict|
22
+ @dictionary = dict
23
+ end
24
+ opts.on("-h", "--help", "Show this message") do
25
+ puts opts
26
+ exit
27
+ end
28
+ opts.on("-r", "--random", "Use a random board") do
29
+ @mode = :random
30
+ end
31
+
32
+ begin
33
+ argv = ["-r"] if argv.empty?
34
+ opts.parse!(argv)
35
+ rescue OptionParser::ParseError => e
36
+ STDERR.puts e.message, "\n", opts
37
+ exit(-1)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ require_relative 'board'
2
+ require_relative 'options'
3
+
4
+ module BoggleSolver
5
+
6
+ class Runner
7
+ def initialize(argv)
8
+ @options = Options.new(argv)
9
+ end
10
+
11
+ def run
12
+ board = Board.new(@options.mode,@options.input,@options.dictionary)
13
+ board.find_words
14
+ p board.words
15
+ p board.score
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,93 @@
1
+ require 'test/unit'
2
+ require 'shoulda'
3
+ require_relative '../lib/boggle_solver/adjacency_matrix'
4
+
5
+ class TestAdjacencyMatrix < Test::Unit::TestCase
6
+
7
+ context "basics" do
8
+ setup do
9
+ @matrix = BoggleSolver::AdjacencyMatrix.new(4)
10
+ end
11
+
12
+ should "print matrix as square" do
13
+ assert_equal "0 0 0 0 \n0 0 0 0 \n0 0 0 0 \n0 0 0 0 ", @matrix.to_s
14
+ end
15
+ end
16
+
17
+ context "directed edges" do
18
+ setup do
19
+ @matrix = BoggleSolver::AdjacencyMatrix.new(4)
20
+ end
21
+
22
+ should "add one directed edge from v1 to v3" do
23
+ @matrix.add_directed_edge(1,3)
24
+ assert_equal "0 0 0 0 \n0 0 0 1 \n0 0 0 0 \n0 0 0 0 ", @matrix.to_s
25
+ end
26
+
27
+ should "add weighted directed edge from v3 to v1" do
28
+ @matrix.add_directed_edge(3,1, 7)
29
+ assert_equal "0 0 0 0 \n0 0 0 0 \n0 0 0 0 \n0 7 0 0 ", @matrix.to_s
30
+ end
31
+
32
+ should "store multiple directed edges" do
33
+ @matrix.add_directed_edge(2,1)
34
+ @matrix.add_directed_edge(3,1)
35
+ @matrix.add_directed_edge(0,3)
36
+ assert_equal "0 0 0 1 \n0 0 0 0 \n0 1 0 0 \n0 1 0 0 ", @matrix.to_s
37
+ end
38
+ end
39
+
40
+ context "undirected edges" do
41
+ setup do
42
+ @matrix = BoggleSolver::AdjacencyMatrix.new(4)
43
+ end
44
+
45
+ should "add undirected edge between v0 and v2" do
46
+ @matrix.add_edge(0,2)
47
+ assert_equal "0 0 1 0 \n0 0 0 0 \n1 0 0 0 \n0 0 0 0 ", @matrix.to_s
48
+ end
49
+
50
+ should "add weighted undirected edge between v0 and v2" do
51
+ @matrix.add_edge(0,2,8)
52
+ assert_equal "0 0 8 0 \n0 0 0 0 \n8 0 0 0 \n0 0 0 0 ", @matrix.to_s
53
+ end
54
+ end
55
+
56
+ context "adjacent" do
57
+ setup do
58
+ @n = 5
59
+ @matrix = BoggleSolver::AdjacencyMatrix.new(@n)
60
+ end
61
+
62
+ should "return empty array since no edges have been added" do
63
+ (0...@n).each { |i| assert_equal [], @matrix.adjacent(i)}
64
+ end
65
+
66
+ should "add 3 edges" do
67
+ @matrix.add_edge(0,1)
68
+ @matrix.add_edge(0,2)
69
+ @matrix.add_edge(0,4)
70
+ assert_equal [1,2,4], @matrix.adjacent(0)
71
+ assert_equal [0], @matrix.adjacent(1)
72
+ assert_equal [0], @matrix.adjacent(2)
73
+ assert_equal [], @matrix.adjacent(3)
74
+ assert_equal [0], @matrix.adjacent(4)
75
+ end
76
+ end
77
+
78
+ context "depth first search" do
79
+ setup do
80
+ @n = 5
81
+ @matrix = BoggleSolver::AdjacencyMatrix.new(@n)
82
+ @matrix.depth_first_search
83
+ end
84
+
85
+ should "visit all verticies" do
86
+ (0...@n).each { |i| assert_not_nil @matrix.visited[i] }
87
+ end
88
+
89
+ should "finish all verticies" do
90
+ (0...@n).each { |i| assert_not_nil @matrix.finished[i] }
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,43 @@
1
+ require 'test/unit'
2
+ require 'shoulda'
3
+ require_relative '../lib/boggle_solver/options'
4
+
5
+ class TestOptions < Test::Unit::TestCase
6
+
7
+ context "specifying nothing" do
8
+ setup do
9
+ @opts = BoggleSolver::Options.new([])
10
+ end
11
+
12
+ should "return default dictionary" do
13
+ assert_equal BoggleSolver::Options::DEFAULT_DICTIONARY, @opts.dictionary
14
+ end
15
+
16
+ should "set mode to random" do
17
+ assert_equal @opts.mode, :random
18
+ end
19
+ end
20
+
21
+ context "specifying a dictionary" do
22
+ should "return it" do
23
+ opts = BoggleSolver::Options.new(["-d", "mydict"])
24
+ assert_equal "mydict", opts.dictionary
25
+ end
26
+ end
27
+
28
+ context "specifying the board with a string and nothing else" do
29
+ should "return the board" do
30
+ opts = BoggleSolver::Options.new(["abcdefghijklmnopqrstuvwxy"])
31
+ assert_equal "abcdefghijklmnopqrstuvwxy", opts.input
32
+ assert_equal opts.mode, :given
33
+ end
34
+ end
35
+
36
+ context "specifying the board with a string and an extra word" do
37
+ should "return only the board" do
38
+ opts = BoggleSolver::Options.new(["abcdefghijklmnopqrstuvwxy", "extra stuff"])
39
+ assert_equal "abcdefghijklmnopqrstuvwxy", opts.input
40
+ assert_equal opts.mode, :given
41
+ end
42
+ end
43
+ end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: boggle_solver
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.0.5
5
+ version: 0.1.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Casey Robinson
@@ -11,16 +11,7 @@ bindir: bin
11
11
  cert_chain: []
12
12
  date: 2012-05-26 00:00:00.000000000Z
13
13
  dependencies: []
14
- description: ! 'Ruby program that employs graph theory to solve a word matrix puzzle
15
- (Boggle).
16
-
17
-
18
- # Introduction
19
-
20
- Boggle Master consists of a 5x5 grid of letters. The goal of the game is to make
21
- as many words as possible from this grid by connecting neighboring letters. To generate
22
- a random puzzle the game uses 25 dice with letters on each side. The dice are shaken
23
- and each fall into a slot in the grid.'
14
+ description: Ruby program that employs graph theory to solve a word matrix puzzle (Boggle).
24
15
  email: kc@rampantmonkey.com
25
16
  executables:
26
17
  - boggle_solver
@@ -35,6 +26,18 @@ files:
35
26
  - lib/boggle_solver/board.rb
36
27
  - lib/boggle_solver/options.rb
37
28
  - lib/boggle_solver/runner.rb
29
+ - pkg/boggle_solver-0.0.5.gem
30
+ - pkg/boggle_solver-0.0.5/boggle_solver.gemspec
31
+ - pkg/boggle_solver-0.0.5/Rakefile
32
+ - pkg/boggle_solver-0.0.5/README.markdown
33
+ - pkg/boggle_solver-0.0.5/bin/boggle_solver
34
+ - pkg/boggle_solver-0.0.5/lib/boggle_solver/adjacency_matrix.rb
35
+ - pkg/boggle_solver-0.0.5/lib/boggle_solver/board.rb
36
+ - pkg/boggle_solver-0.0.5/lib/boggle_solver/options.rb
37
+ - pkg/boggle_solver-0.0.5/lib/boggle_solver/runner.rb
38
+ - pkg/boggle_solver-0.0.5/test/test_adjacency_matrix.rb
39
+ - pkg/boggle_solver-0.0.5/test/test_board.rb
40
+ - pkg/boggle_solver-0.0.5/test/test_options.rb
38
41
  - test/test_adjacency_matrix.rb
39
42
  - test/test_board.rb
40
43
  - test/test_options.rb