boggler 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +4 -0
- data.tar.gz.sig +3 -0
- data/bin/boggler +3 -0
- data/bin/boggler_solver +3 -0
- data/lib/boggler.rb +24 -0
- data/lib/boggler/benchmarking.rb +15 -0
- data/lib/boggler/cell.rb +95 -0
- data/lib/boggler/core.rb +41 -0
- data/lib/boggler/dice.rb +83 -0
- data/lib/boggler/dictionary.rb +76 -0
- data/lib/boggler/die.rb +18 -0
- data/lib/boggler/grid.rb +94 -0
- data/lib/boggler/solver.rb +27 -0
- data/lib/boggler/version.rb +3 -0
- data/readme.md +160 -0
- data/vendor/dictionary.txt +62916 -0
- metadata +115 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 86259e476a7f531b684261ce5062509d9500c37e
|
4
|
+
data.tar.gz: 7da27f1ea047c7fad9a7fa8690980435b2368b13
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b1b71f7431211d276e4a96283ab96c674e363ccb1a37eb14a6670aaa9e95ef4cc0186a0f664b1c2d3d213da2a1b04cb55a3eafc9b64f19e38d06acf3cbd5cd9b
|
7
|
+
data.tar.gz: 5653dfac6b0ba0daec843046192aa99b8accaf37b8039766cd2495c03e5367e0d7a01883de09cd380e8e6aafc49ecf9a5341876db7fd3c09baa7785c66e557b5
|
checksums.yaml.gz.sig
ADDED
data.tar.gz.sig
ADDED
data/bin/boggler
ADDED
data/bin/boggler_solver
ADDED
data/lib/boggler.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Boggler
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def load
|
7
|
+
require File.join(load_path, 'core')
|
8
|
+
require File.join(load_path, 'die')
|
9
|
+
require File.join(load_path, 'dice')
|
10
|
+
require File.join(load_path, 'grid')
|
11
|
+
require File.join(load_path, 'cell')
|
12
|
+
require File.join(load_path, 'dictionary')
|
13
|
+
require File.join(load_path, 'benchmarking')
|
14
|
+
require File.join(load_path, 'solver')
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def load_path
|
20
|
+
@load_path ||= File.expand_path('../boggler', __FILE__)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Boggler.load
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
|
3
|
+
module Boggler
|
4
|
+
module Benchmarking
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def measure(description, &block)
|
8
|
+
benchmark_format = "%n\t#{Benchmark::FORMAT}"
|
9
|
+
|
10
|
+
puts Benchmark.measure(description) {
|
11
|
+
yield if block_given?
|
12
|
+
}.format(benchmark_format)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/boggler/cell.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
module Boggler
|
2
|
+
class Cell
|
3
|
+
attr_reader :row, :column, :letter, :word, :previous
|
4
|
+
|
5
|
+
MOVES = [
|
6
|
+
[-1, -1],
|
7
|
+
[-1, 0],
|
8
|
+
[-1, 1],
|
9
|
+
[ 0, -1],
|
10
|
+
[ 0, 1],
|
11
|
+
[ 1, -1],
|
12
|
+
[ 1, 0],
|
13
|
+
[ 1, 1],
|
14
|
+
]
|
15
|
+
|
16
|
+
def initialize(
|
17
|
+
grid, row, column, dictionary = nil, used = nil, previous = nil)
|
18
|
+
@row = row
|
19
|
+
@column = column
|
20
|
+
@letter = grid[row - 1][column - 1]
|
21
|
+
@grid = grid
|
22
|
+
@previous = previous
|
23
|
+
@dictionary = dictionary || Dictionary.words
|
24
|
+
|
25
|
+
set_used used
|
26
|
+
@used[row][column] = true
|
27
|
+
|
28
|
+
@word = ((previous && previous.word) || '') + @letter
|
29
|
+
set_dictionary
|
30
|
+
end
|
31
|
+
|
32
|
+
def next_cell
|
33
|
+
begin
|
34
|
+
iterator.next
|
35
|
+
rescue StopIteration
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def valid_word?
|
41
|
+
!!@dictionary['']
|
42
|
+
end
|
43
|
+
|
44
|
+
def possible_word?
|
45
|
+
!!@dictionary
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def set_dictionary
|
51
|
+
case word.length
|
52
|
+
when 1, 2
|
53
|
+
@dictionary = {}
|
54
|
+
when 3
|
55
|
+
@dictionary = Dictionary.words[@word]
|
56
|
+
else
|
57
|
+
@dictionary = @dictionary[@word[-1]]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def set_used(used)
|
62
|
+
@used = used && used.dup
|
63
|
+
@used ||= {}
|
64
|
+
|
65
|
+
@grid.size.times do |i|
|
66
|
+
row = used && used[i + 1] && used[i + 1].dup
|
67
|
+
@used[i + 1] = row || {}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def iterator
|
72
|
+
@iterator ||= neighbors.each
|
73
|
+
end
|
74
|
+
|
75
|
+
def neighbors
|
76
|
+
return @neighbors if @neighbors
|
77
|
+
|
78
|
+
@neighbors = []
|
79
|
+
|
80
|
+
MOVES.each do |move|
|
81
|
+
next_row = row + move.first
|
82
|
+
next_column = column + move.last
|
83
|
+
|
84
|
+
if next_row > 0 && next_row <= @grid.size &&
|
85
|
+
next_column > 0 && next_column <= @grid.size &&
|
86
|
+
!@used[next_row][next_column]
|
87
|
+
@neighbors << Cell.new(
|
88
|
+
@grid, next_row, next_column, @dictionary, @used, self)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
@neighbors
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/boggler/core.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Boggler
|
2
|
+
module Core
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def run
|
6
|
+
start
|
7
|
+
Solver.solve @grid
|
8
|
+
end
|
9
|
+
|
10
|
+
def game
|
11
|
+
start
|
12
|
+
|
13
|
+
time = Time.now
|
14
|
+
Dictionary.words
|
15
|
+
while Time.now - time < 3 * 60 do
|
16
|
+
end
|
17
|
+
|
18
|
+
puts "\n" * 100
|
19
|
+
|
20
|
+
Solver.solve @grid
|
21
|
+
end
|
22
|
+
|
23
|
+
def seed
|
24
|
+
return @seed if defined?(@seed)
|
25
|
+
#@seed = 7567 # postdated appears
|
26
|
+
#@seed = 532183 # used for construction of grid word construction
|
27
|
+
@seed = rand(999_999)
|
28
|
+
srand @seed
|
29
|
+
@seed
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def start
|
35
|
+
seed
|
36
|
+
puts "Randomized with seed #{seed}"
|
37
|
+
@grid = Grid.new
|
38
|
+
puts @grid.to_s
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/boggler/dice.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Boggler
|
2
|
+
module Dice
|
3
|
+
extend self
|
4
|
+
|
5
|
+
BOGGLE_DICE = [
|
6
|
+
Die.new(%W{a a c i o t}),
|
7
|
+
Die.new(%W{a h m o r s}),
|
8
|
+
Die.new(%W{e g k l u y}),
|
9
|
+
Die.new(%W{a b i l t y}),
|
10
|
+
Die.new(%W{a c d e m p}),
|
11
|
+
Die.new(%W{e g i n t v}),
|
12
|
+
Die.new(%W{g i l r u w}),
|
13
|
+
Die.new(%W{e l p s t u}),
|
14
|
+
Die.new(%W{d e n o s w}),
|
15
|
+
Die.new(%W{a c e l r s}),
|
16
|
+
Die.new(%W{a b j m o q}),
|
17
|
+
Die.new(%W{e e f h i y}),
|
18
|
+
Die.new(%W{e h i n p s}),
|
19
|
+
Die.new(%W{d k n o t u}),
|
20
|
+
Die.new(%W{a d e n v z}),
|
21
|
+
Die.new(%W{b i f o r x}),
|
22
|
+
]
|
23
|
+
|
24
|
+
STATIC_BOGGLE = [
|
25
|
+
Die.new(%W{e g i n t v}),
|
26
|
+
Die.new(%W{e l p s t u}),
|
27
|
+
Die.new(%W{a b j m o q}),
|
28
|
+
Die.new(%W{e h i n p s}),
|
29
|
+
Die.new(%W{d k n o t u}),
|
30
|
+
Die.new(%W{a d e n v z}),
|
31
|
+
Die.new(%W{b i f o r x}),
|
32
|
+
].shuffle
|
33
|
+
|
34
|
+
STATIC_DICE = [
|
35
|
+
Die.new(['h']),
|
36
|
+
Die.new(['e']),
|
37
|
+
Die.new(['a']),
|
38
|
+
Die.new(['r']),
|
39
|
+
STATIC_BOGGLE[0],
|
40
|
+
Die.new(['o']),
|
41
|
+
Die.new(['r']),
|
42
|
+
STATIC_BOGGLE[1],
|
43
|
+
STATIC_BOGGLE[2],
|
44
|
+
STATIC_BOGGLE[3],
|
45
|
+
Die.new(['m']),
|
46
|
+
STATIC_BOGGLE[4],
|
47
|
+
STATIC_BOGGLE[5],
|
48
|
+
STATIC_BOGGLE[6],
|
49
|
+
Die.new(['y']),
|
50
|
+
Die.new(['e']),
|
51
|
+
]
|
52
|
+
|
53
|
+
def get(opts = {})
|
54
|
+
opts ||= {}
|
55
|
+
|
56
|
+
case opts[:method]
|
57
|
+
when :random
|
58
|
+
random_dice
|
59
|
+
when :boggle
|
60
|
+
BOGGLE_DICE.shuffle
|
61
|
+
when :big_boggle
|
62
|
+
[
|
63
|
+
BOGGLE_DICE.shuffle,
|
64
|
+
BOGGLE_DICE.shuffle,
|
65
|
+
BOGGLE_DICE.shuffle,
|
66
|
+
BOGGLE_DICE.shuffle,
|
67
|
+
].flatten
|
68
|
+
when :static
|
69
|
+
STATIC_DICE
|
70
|
+
else
|
71
|
+
BOGGLE_DICE.shuffle
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def random_dice
|
78
|
+
16.times.map do
|
79
|
+
Die.new
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Boggler
|
2
|
+
module Dictionary
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def words
|
6
|
+
return @words if defined?(@words)
|
7
|
+
|
8
|
+
content = nil
|
9
|
+
|
10
|
+
Benchmarking.measure('dictionary') do
|
11
|
+
path = File.expand_path('../../../vendor/dictionary.txt', __FILE__)
|
12
|
+
content = File.read(path)
|
13
|
+
content = content.split("\n")
|
14
|
+
@words = words_to_hash(content)
|
15
|
+
end
|
16
|
+
|
17
|
+
@words
|
18
|
+
end
|
19
|
+
|
20
|
+
def words_to_hash(words)
|
21
|
+
hash = {}
|
22
|
+
|
23
|
+
words.each do |word|
|
24
|
+
word = word.split('')
|
25
|
+
|
26
|
+
key = word[0..2].join
|
27
|
+
letters = word[3..-1]
|
28
|
+
|
29
|
+
merge_word hash, key, word_to_hash(letters)
|
30
|
+
end
|
31
|
+
|
32
|
+
hash
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def merge_word(hash, key, word_hash)
|
38
|
+
if hash[key]
|
39
|
+
merge_word_hash hash[key], word_hash
|
40
|
+
else
|
41
|
+
hash[key] = word_hash
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def merge_word_hash(hash, word_hash)
|
46
|
+
hash.merge!(word_hash) do |key, old_value, new_value|
|
47
|
+
if old_value.is_a?(Hash) && new_value.is_a?(Hash)
|
48
|
+
merge_word_hash(old_value, new_value)
|
49
|
+
else
|
50
|
+
old_value || new_value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def word_to_hash(letters)
|
56
|
+
if letters.empty?
|
57
|
+
{'' => true}
|
58
|
+
else
|
59
|
+
letters_to_hash({}, letters)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def letters_to_hash(hash = {}, letters = [])
|
64
|
+
hash ||= {}
|
65
|
+
letters.each_with_index do |letter, i|
|
66
|
+
if letters.length > 1
|
67
|
+
hash[letter] = letters_to_hash(hash[letter], letters[(i + 1)..-1])
|
68
|
+
return hash
|
69
|
+
else
|
70
|
+
hash[letter] = {'' => true}
|
71
|
+
return hash
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/boggler/die.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Boggler
|
2
|
+
class Die
|
3
|
+
def initialize(sides = [])
|
4
|
+
@sides = sides || []
|
5
|
+
@sides << random_letter if sides.empty?
|
6
|
+
end
|
7
|
+
|
8
|
+
def roll
|
9
|
+
@sides.sample
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def random_letter
|
15
|
+
rand(10...36).to_s 36
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/boggler/grid.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
module Boggler
|
2
|
+
class Grid
|
3
|
+
def initialize
|
4
|
+
@grid = []
|
5
|
+
|
6
|
+
@dice = Dice.get
|
7
|
+
@size = Math.sqrt(@dice.length).to_i
|
8
|
+
|
9
|
+
build_grid
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
row_strings = []
|
14
|
+
|
15
|
+
@grid.each do |row|
|
16
|
+
row_strings << row_string_for(row)
|
17
|
+
end
|
18
|
+
|
19
|
+
grid_string = separator
|
20
|
+
row_strings.each do |row|
|
21
|
+
grid_string += row
|
22
|
+
grid_string += separator
|
23
|
+
end
|
24
|
+
|
25
|
+
grid_string
|
26
|
+
end
|
27
|
+
|
28
|
+
def words
|
29
|
+
return @words if defined?(@words)
|
30
|
+
|
31
|
+
@words = []
|
32
|
+
|
33
|
+
@size.times do |i|
|
34
|
+
@size.times do |j|
|
35
|
+
words_starting_at(i + 1, j + 1)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
@words
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def words_starting_at(row, column)
|
45
|
+
start_cell = Cell.new(@grid, row, column)
|
46
|
+
words_from_cell start_cell
|
47
|
+
end
|
48
|
+
|
49
|
+
def words_from_cell(cell)
|
50
|
+
@words << cell.word if cell.valid_word?
|
51
|
+
next_cell = next_possible_cell(cell)
|
52
|
+
words_from_cell(next_cell) if next_cell
|
53
|
+
end
|
54
|
+
|
55
|
+
def next_possible_cell(cell)
|
56
|
+
return unless cell
|
57
|
+
candidate = cell && cell.next_cell
|
58
|
+
while candidate && !candidate.possible_word?
|
59
|
+
candidate = cell.next_cell
|
60
|
+
end
|
61
|
+
|
62
|
+
return candidate if candidate
|
63
|
+
|
64
|
+
next_possible_cell cell.previous
|
65
|
+
end
|
66
|
+
|
67
|
+
def build_grid
|
68
|
+
letters = []
|
69
|
+
|
70
|
+
@dice.each_with_index do |die, i|
|
71
|
+
if i % @size == 0
|
72
|
+
@grid << letters unless letters.empty?
|
73
|
+
letters = []
|
74
|
+
end
|
75
|
+
|
76
|
+
letters << die.roll
|
77
|
+
end
|
78
|
+
|
79
|
+
@grid << letters
|
80
|
+
end
|
81
|
+
|
82
|
+
def row_string_for(row)
|
83
|
+
row_string = '|'
|
84
|
+
row.each do |cell|
|
85
|
+
row_string += " #{cell.upcase} |"
|
86
|
+
end
|
87
|
+
row_string + "\n"
|
88
|
+
end
|
89
|
+
|
90
|
+
def separator
|
91
|
+
@sep ||= '-' + ('-' * (4 * @size)) + "\n"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|