boggle_solver 0.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/README.markdown +59 -0
- data/bin/boggle_solver +6 -0
- data/boggle_solver.gemspec +16 -0
- data/lib/boggle_solver/adjacency_matrix.rb +52 -0
- data/lib/boggle_solver/board.rb +124 -0
- data/lib/boggle_solver/runner.rb +17 -0
- metadata +54 -0
data/README.markdown
ADDED
@@ -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
|
data/bin/boggle_solver
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "boggle_solver"
|
3
|
+
s.summary = "Find all words in a letter matrix"
|
4
|
+
s.description = "File.read(File.join(File.dirname(__FILE__), 'README'))"
|
5
|
+
s.requirements = "[ 'An installed dictionary (most Unix systems have one)']"
|
6
|
+
s.version = "0.0.1"
|
7
|
+
s.author = "Casey Robinson"
|
8
|
+
s.email = "kc@rampantmonkey.com"
|
9
|
+
s.homepage = "http://rampantmonkey.com"
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.required_ruby_version = '>=1.9'
|
12
|
+
s.files = Dir['**/**']
|
13
|
+
s.executables = [ 'boggle_solver' ]
|
14
|
+
s.test_files = Dir["test/test*.rb"]
|
15
|
+
s.has_rdoc = false
|
16
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module BoggleSolver
|
2
|
+
|
3
|
+
class AdjacencyMatrix
|
4
|
+
|
5
|
+
def initialize( n = 2 )
|
6
|
+
@m = Array.new(n, 0)
|
7
|
+
@m.map!{Array.new(n, 0)}
|
8
|
+
@n = n
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
s = ""
|
13
|
+
@m.each do |row|
|
14
|
+
row.each {|e| s += e.to_s + " "}
|
15
|
+
s += "\n"
|
16
|
+
end
|
17
|
+
s.chop
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_directed_edge(i, j, w=1)
|
21
|
+
@m[i][j] = w unless i == j
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_edge(i, j, w=1)
|
25
|
+
add_directed_edge(i, j, w)
|
26
|
+
add_directed_edge(j, i, w)
|
27
|
+
end
|
28
|
+
|
29
|
+
def adjacent(u)
|
30
|
+
a = Array.new
|
31
|
+
@m[u].each_with_index {|v,i| a.push i unless v == 0 }
|
32
|
+
a
|
33
|
+
end
|
34
|
+
|
35
|
+
def depth_first_search
|
36
|
+
@visited = Array.new(@n, nil)
|
37
|
+
@finished = Array.new(@n, nil)
|
38
|
+
@visited.each_with_index do |visit_status, u|
|
39
|
+
depth_first_search_visit(u) if visit_status.nil?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def depth_first_search_visit(u, *)
|
44
|
+
@visited[u] = 1
|
45
|
+
puts u
|
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,124 @@
|
|
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
|
10
|
+
super(25)
|
11
|
+
connect
|
12
|
+
@board = []
|
13
|
+
# @prng = Random.new
|
14
|
+
@words = []
|
15
|
+
generate_board
|
16
|
+
load_dictionary
|
17
|
+
end
|
18
|
+
|
19
|
+
def connect
|
20
|
+
j=0;
|
21
|
+
while j<25 do
|
22
|
+
add_edge(j, j-6) unless j < 6 or j % 5 == 0
|
23
|
+
add_edge(j, j-5) unless j < 5
|
24
|
+
add_edge(j, j-4) unless j < 4 or j % 5 == 4
|
25
|
+
add_edge(j, j-1) unless j % 5 == 0
|
26
|
+
add_edge(j, j+1) unless j % 5 == 4
|
27
|
+
add_edge(j, j+4) unless j > 19 or j % 5 == 0
|
28
|
+
add_edge(j, j+5) unless j > 19
|
29
|
+
add_edge(j, j+6) unless j > 18 or j % 5 == 4
|
30
|
+
j += 1
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def generate_board
|
35
|
+
# @board.map!{|item| (65.+rand(25)).chr}
|
36
|
+
@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"]
|
37
|
+
@board_hash = Hash.new(0)
|
38
|
+
@board.each {|letter| @board_hash[letter] += 1}
|
39
|
+
end
|
40
|
+
|
41
|
+
def load_dictionary
|
42
|
+
dictionary_file = "/usr/share/dict/words"
|
43
|
+
@dictionary = Array.new
|
44
|
+
|
45
|
+
File.foreach(dictionary_file) do |line|
|
46
|
+
next_word = line.chomp
|
47
|
+
@dictionary.push next_word if possible? next_word
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def possible? word
|
52
|
+
word_hash = Hash.new(0)
|
53
|
+
result = true
|
54
|
+
word.each_char {|letter| word_hash[letter] += 1}
|
55
|
+
word_hash.each do |letter, count|
|
56
|
+
result &&= count <= @board_hash[letter]
|
57
|
+
end
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
s = ""
|
63
|
+
@board.each_with_index do |letter, index|
|
64
|
+
s += letter.to_s + " "
|
65
|
+
s += "\n" if index % 5 == 4
|
66
|
+
end
|
67
|
+
s.chomp
|
68
|
+
end
|
69
|
+
|
70
|
+
def in_dictionary? s
|
71
|
+
@dictionary.index s
|
72
|
+
end
|
73
|
+
|
74
|
+
def part_in_dictionary? s
|
75
|
+
s = "\\A" + s + "..*"
|
76
|
+
regex = Regexp.new(s)
|
77
|
+
!(@dictionary.grep regex).empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_words
|
81
|
+
threads = []
|
82
|
+
@board.each_with_index do |letter, index|
|
83
|
+
threads << Thread.new { depth_first_search(index) }
|
84
|
+
end
|
85
|
+
|
86
|
+
threads.each do |t|
|
87
|
+
t.join
|
88
|
+
@words.concat t[:words]
|
89
|
+
end
|
90
|
+
|
91
|
+
@words.uniq!
|
92
|
+
@words.sort_by!{|item| item.length}
|
93
|
+
end
|
94
|
+
|
95
|
+
def depth_first_search(offset=0)
|
96
|
+
Thread.current[:visited] = Array.new(@n, nil)
|
97
|
+
Thread.current[:finished] = Array.new(@n, nil)
|
98
|
+
Thread.current[:words] = []
|
99
|
+
Thread.current[:visited][offset..-1].each_with_index do |visit_status, u|
|
100
|
+
u += offset
|
101
|
+
search_string = @board[u]
|
102
|
+
depth_first_search_visit(u, search_string) if Thread.current[:finished][u].nil?
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def depth_first_search_visit(u, search_string)
|
107
|
+
Thread.current[:visited][u] = 1
|
108
|
+
Thread.current[:words] << search_string if in_dictionary? search_string
|
109
|
+
adjacent(u).each do |v|
|
110
|
+
if Thread.current[:visited][v].nil?
|
111
|
+
s = search_string + @board[v]
|
112
|
+
Thread.current[:words].push s if in_dictionary? s
|
113
|
+
if part_in_dictionary? s
|
114
|
+
depth_first_search_visit(v, s)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
Thread.current[:finished][u] = 1
|
119
|
+
Thread.current[:visited][u] = nil
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: boggle_solver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Casey Robinson
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-15 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: File.read(File.join(File.dirname(__FILE__), 'README'))
|
15
|
+
email: kc@rampantmonkey.com
|
16
|
+
executables:
|
17
|
+
- boggle_solver
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- boggle_solver-0.0.1.gem
|
22
|
+
- boggle_solver.gemspec
|
23
|
+
- README.markdown
|
24
|
+
- bin/boggle_solver
|
25
|
+
- lib/boggle_solver/adjacency_matrix.rb
|
26
|
+
- lib/boggle_solver/board.rb
|
27
|
+
- lib/boggle_solver/runner.rb
|
28
|
+
homepage: http://rampantmonkey.com
|
29
|
+
licenses: []
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '1.9'
|
39
|
+
none: false
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- ! '[ ''An installed dictionary (most Unix systems have one)'']'
|
48
|
+
rubyforge_project:
|
49
|
+
rubygems_version: 1.8.15
|
50
|
+
signing_key:
|
51
|
+
specification_version: 3
|
52
|
+
summary: Find all words in a letter matrix
|
53
|
+
test_files: []
|
54
|
+
...
|