boggle_solver 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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