word-search-puzzle 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 9294041e86cf760a8de12fda97d4217675d7ba8bc424ea039d61411a1a62167e
4
+ data.tar.gz: 9eba51b91b9a55347eca122e07c81782877bdf16d4e2e2f966e8840d3ccbaa1a
5
+ SHA512:
6
+ metadata.gz: 16f10a31d33c73f88d26ade1245ab20bf10eee17e58ba30e1858f0aba2fa5972e0a90809103180424b01a11756bd915f2d223845ce897dfa4ec2ec303fb9660f
7
+ data.tar.gz: 20281180b73200edf1b907e2a0a4201615abfa9d66a6f8e32763f7789b0579ec130e4947238f2b32e951493a8a5982f9734711ed272a48363603ff9825b26cc0
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.2
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/testdouble/standard
3
+ ruby_version: 3.4
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-10-13
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in word-search-puzzle.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "test-unit", "~> 3.0"
10
+ gem "standard", "~> 1.3"
11
+
12
+ # warning:
13
+ # <These gems> were loaded from the standard library,
14
+ # but will no longer be part of the default gems starting from Ruby 3.5.0.
15
+ # You can add irb to your Gemfile or gemspec to silence this warning.
16
+ gem "rdoc", "~> 6.15"
17
+ gem "irb", "~> 1.15"
data/Gemfile.lock ADDED
@@ -0,0 +1,98 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ word-search-puzzle (0.1.0)
5
+ colorize (~> 1.1.0)
6
+ thor (~> 1.4.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ ast (2.4.3)
12
+ colorize (1.1.0)
13
+ date (3.4.1)
14
+ erb (5.1.1)
15
+ io-console (0.8.1)
16
+ irb (1.15.2)
17
+ pp (>= 0.6.0)
18
+ rdoc (>= 4.0.0)
19
+ reline (>= 0.4.2)
20
+ json (2.15.0)
21
+ language_server-protocol (3.17.0.5)
22
+ lint_roller (1.1.0)
23
+ parallel (1.27.0)
24
+ parser (3.3.9.0)
25
+ ast (~> 2.4.1)
26
+ racc
27
+ power_assert (2.0.5)
28
+ pp (0.6.3)
29
+ prettyprint
30
+ prettyprint (0.2.0)
31
+ prism (1.5.2)
32
+ psych (5.2.6)
33
+ date
34
+ stringio
35
+ racc (1.8.1)
36
+ rainbow (3.1.1)
37
+ rake (13.3.0)
38
+ rdoc (6.15.0)
39
+ erb
40
+ psych (>= 4.0.0)
41
+ tsort
42
+ regexp_parser (2.11.3)
43
+ reline (0.6.2)
44
+ io-console (~> 0.5)
45
+ rubocop (1.80.2)
46
+ json (~> 2.3)
47
+ language_server-protocol (~> 3.17.0.2)
48
+ lint_roller (~> 1.1.0)
49
+ parallel (~> 1.10)
50
+ parser (>= 3.3.0.2)
51
+ rainbow (>= 2.2.2, < 4.0)
52
+ regexp_parser (>= 2.9.3, < 3.0)
53
+ rubocop-ast (>= 1.46.0, < 2.0)
54
+ ruby-progressbar (~> 1.7)
55
+ unicode-display_width (>= 2.4.0, < 4.0)
56
+ rubocop-ast (1.47.1)
57
+ parser (>= 3.3.7.2)
58
+ prism (~> 1.4)
59
+ rubocop-performance (1.25.0)
60
+ lint_roller (~> 1.1)
61
+ rubocop (>= 1.75.0, < 2.0)
62
+ rubocop-ast (>= 1.38.0, < 2.0)
63
+ ruby-progressbar (1.13.0)
64
+ standard (1.51.1)
65
+ language_server-protocol (~> 3.17.0.2)
66
+ lint_roller (~> 1.0)
67
+ rubocop (~> 1.80.2)
68
+ standard-custom (~> 1.0.0)
69
+ standard-performance (~> 1.8)
70
+ standard-custom (1.0.2)
71
+ lint_roller (~> 1.0)
72
+ rubocop (~> 1.50)
73
+ standard-performance (1.8.0)
74
+ lint_roller (~> 1.1)
75
+ rubocop-performance (~> 1.25.0)
76
+ stringio (3.1.7)
77
+ test-unit (3.7.0)
78
+ power_assert
79
+ thor (1.4.0)
80
+ tsort (0.2.0)
81
+ unicode-display_width (3.2.0)
82
+ unicode-emoji (~> 4.1)
83
+ unicode-emoji (4.1.0)
84
+
85
+ PLATFORMS
86
+ ruby
87
+ x86_64-linux
88
+
89
+ DEPENDENCIES
90
+ irb (~> 1.15)
91
+ rake (~> 13.0)
92
+ rdoc (~> 6.15)
93
+ standard (~> 1.3)
94
+ test-unit (~> 3.0)
95
+ word-search-puzzle!
96
+
97
+ BUNDLED WITH
98
+ 2.7.2
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Word Search Puzzle
2
+
3
+ Ruby gem to create word search puzzles.
4
+
5
+ ## Installation
6
+
7
+ Install the gem by executing:
8
+
9
+ $ gem install word-search-puzzle
10
+
11
+ ## Usage
12
+
13
+ Execute `word-search-puzzle` from the command line.
14
+
15
+ **Example**: create puzzle with default options.
16
+
17
+ ```
18
+ $ word-search-puzzle create --words=JEDI,SITH,STARWARS
19
+ ```
20
+ ![](docs/images/puzzle-01.png)
21
+
22
+ > Each execution builds a different puzzle.
23
+
24
+ **Example**: Create a puzzle showing the final solution.
25
+ ```
26
+ $ word-search-puzzle create --words=VADER,OBIWAN,LUKE --padding='.' --size=6x6
27
+ . R . . . .
28
+ . . E . . .
29
+ . . . D . .
30
+ O B I W A N
31
+ . . . . . V
32
+ E K U L . .
33
+ ```
34
+
35
+ **Example**: Create puzzle reading words from text file.
36
+ ```
37
+ $ word-search-puzzle create --words=examples/starwars.txt
38
+ ```
39
+ ![](docs/images/puzzle-03.png)
40
+
41
+ > **WARNING:**
42
+ > Depending on the number and size of the words, the size of the grid, and the numbers of gaps, sometimes it will not be possible to find a solution.
43
+
44
+ ## Documentation
45
+
46
+ * [Command line options and examples](docs/options.md)
47
+ * [Examples using the Ruby library](examples/)
48
+ * [Development](docs/development.md)
49
+
50
+ ## Contributing
51
+
52
+ Bug reports and pull requests are welcome on GitHub at https://github.com/dvarrui/word-search-puzzle.
53
+
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ require "standard/rake"
13
+
14
+ task default: %i[test standard]
15
+
16
+ task :help do
17
+ puts "Usage: rake --tasks or rake --help"
18
+ end
@@ -0,0 +1,19 @@
1
+ [<< back](../README.md)
2
+
3
+ # Development
4
+
5
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
6
+
7
+ * To install this gem onto your local machine, run `bundle exec rake install`.
8
+ * To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
9
+
10
+ ## Installation
11
+
12
+ Install the gem by executing:
13
+
14
+ $ gem install word-search-puzzle
15
+
16
+ If bundler is being used to manage dependencies, install the gem and add to the application's Gemfile by executing:
17
+
18
+ $ bundle add word-search-puzzle
19
+
Binary file
Binary file
Binary file
data/docs/options.md ADDED
@@ -0,0 +1,157 @@
1
+ [<< back](../README.md)
2
+
3
+ # Command line options
4
+
5
+ ## Help
6
+
7
+ * Show help: `word-search-puzzle help`
8
+ * Show **create** action help: `word-search-puzzle help create`
9
+
10
+ # Building options
11
+
12
+ > Used to create different puzzles.
13
+
14
+ * `--words`, Comma separated list of word or file name with de words.
15
+ * `--size`, Grid size. Default value 10x10.
16
+
17
+ ## Words options
18
+
19
+ **Example**: Create puzzle using these words: JEDO, SITH, STARWARS.
20
+ ```
21
+ $ word-search-puzzle create --words=JEDI,SITH,STARWARS
22
+ D S O I D P O V W K
23
+ N J E D I D H P Q T
24
+ Q S T L M J Z N R M
25
+ L T O Y U B S B L P
26
+ N A K H W Q I K C K
27
+ G R Y D E I T R I N
28
+ N W E F H U H U F H
29
+ R A B S J U G M K U
30
+ U R V M A F H W D H
31
+ Q S O S L I V R O C
32
+ ```
33
+
34
+ **Example**: Create puzzle reading words from TXT file.
35
+ ```
36
+ $ word-search-puzzle create --words=examples/starwars.txt
37
+ A W L U K E D W Y G
38
+ O P 3 C R C D G W Z
39
+ S R A W R A T S E S
40
+ T Y O D A D N U Q L
41
+ V R O D A R E P M E
42
+ A R Y O B I W A N C
43
+ D H 2 W D F L I G K
44
+ E Y S D X S N E D N
45
+ R V H U 2 E B V I W
46
+ R J F S I T H G S A
47
+ ```
48
+
49
+ ## Size option
50
+
51
+ **Example**: Create default size puzzle (10 rows and 10 cols).
52
+ ```
53
+ $ word-search-puzzle create --words=examples/starwars.txt
54
+ X U B R E D A V O B
55
+ R N S A D O Y B E T
56
+ 2 A R R F M I Z M Z
57
+ D X A I L W Q J P D
58
+ 2 H W F A U D E E N
59
+ C T R N L M K S R X
60
+ H J A B H A S E A U
61
+ G V T W H I I O D N
62
+ E R S T S E T S O X
63
+ Q C 3 P O L H W R M
64
+ ```
65
+
66
+ **Example**: Create 8x20 puzzle (8 rows and 20 cols).
67
+ ```
68
+ $ word-search-puzzle create --words=examples/starwars.txt --size=8x20
69
+ C E K U L K W S S I Y I G O B I W A N X
70
+ L E F A T F S L W O C Y R 2 D 2 I Z P P
71
+ E Y V S J R N U D O Y L R H X Q O Q N O
72
+ Y H W T I C H A T V V J F G K R E O M Z
73
+ P N V Y P S P G J J R I J O P 3 C I S O
74
+ K Z K X T R O D A R E P M E T L E I A N
75
+ S R A W R A T S X Z E L O B A W T P Y O
76
+ D S L L G M N C A V A D E R O H C L L G
77
+ ```
78
+
79
+ *+Example**: Create 9x9 puzzle (rows and 9 cols).
80
+ ```
81
+ $ word-search-puzzle create --words=examples/starwars.txt --size=9
82
+ X R I Y O Y S V B
83
+ T O O Q B L R H Z
84
+ J D C V I U A T T
85
+ A A K A W K W I O
86
+ O R L D A E R S P
87
+ N E E E N 2 A I 3
88
+ M P I R D J T K C
89
+ L M A 2 K Z S V T
90
+ R E R G I P N Z W
91
+ ```
92
+ # Rendering options
93
+
94
+ > Used to display the puzzle in different ways.
95
+
96
+ * `--color`, Indicates whether to display the output with color. Default value false.
97
+ * `--padding=LIST`, comma.separated list of characters to fill in the gaps in the puzzle. Default value A-Z.
98
+ * `--gaps=FILEPATH`, Filename with list of gaps coordinates. List of `row,col` integers.
99
+
100
+ ## Color option
101
+
102
+ The `color` option, highlights the list of words within the puzzle, so we can easily identify their location.
103
+
104
+ ```
105
+ $ word-search-puzzle create --words=DEATH,STAR --color
106
+ ```
107
+ ![](images/color-option.png)
108
+
109
+ ## Padding option
110
+
111
+ In the puzzle creation process, we first place the word list inside the grid and then fill in the blanks with random letters until the grid is completely filled.
112
+
113
+ During this step, the letters A through Z will be used by default as random values.
114
+
115
+ However, in some cases, the words in our puzzle may use other characters set (for example, Japanese characters), so we use `padding` option set custom random character fill list
116
+
117
+ **Example**: Using `padding` option to custom random padding values.
118
+
119
+ ```
120
+ $ word-search-puzzle create --words=DEATH,STAR --padding='+,*,x'
121
+ * x * + x x * * x x
122
+ * x x + x * * * * x
123
+ + x * * + x * * + +
124
+ + + x x + x + * x +
125
+ * * * + * + + + + x
126
+ + * x + * + H x * x
127
+ + * + + x T + + x *
128
+ + x + x A * + + + +
129
+ x x x E * + R A T S
130
+ x x D * x + * * x x
131
+ ```
132
+
133
+ ## Gaps option
134
+
135
+ The grid is a group of cell into square or a rectangle shape. However, this program offers us to customize the grid shape.
136
+
137
+ To do this, first we start with a square grid and gradually remove the cells defined by `gaps` option to customize the shape.
138
+
139
+ **Example**: Using `gaps` to customize grid shape.
140
+
141
+ ```
142
+ $ word-search-puzzle create --words=DEATH,STAR --gaps=examples/ball-shape.txt
143
+
144
+ N L P T
145
+ O R P S P O
146
+ V E A S T D P Q
147
+ Y O B B A H C X
148
+ O P B M R T W K
149
+ V O I P K A F T
150
+ H H J N E X
151
+ U J D D
152
+
153
+ ```
154
+
155
+ We can define gaps in two ways:
156
+ 1. Using a [TXT file](../examples/ball-shape.txt), where the dot (`.`) character represents a gap and `@` represents an available cell.
157
+ 2. Using a [CSV file](../examples/ball-gaps.csv), where each line contains the coordinates (row, column) of a gap.
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # Create puzzle with default options
3
+
4
+ require_relative "../lib/word-search-puzzle"
5
+
6
+ # 1. Initialize your words list
7
+ words = %w[STARWARS OBIWAN LUKE VADER R2D2 C3PO LEIA YODA EMPERADOR SITH]
8
+
9
+ # 2. Create puzzle (Default size 10x10)
10
+ puzzle = WordSearchPuzzle.create(words: words)
11
+
12
+ # 3. Show puzzle on screen
13
+ puts puzzle.render
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ # Create your puzzle reading words from external txt file
3
+
4
+ require_relative "../lib/word-search-puzzle"
5
+
6
+ # 1. Create puzzle (default size 10x10) reading words from text file
7
+ FILEPATH = File.join(File.dirname(__FILE__), "starwars.txt")
8
+ puzzle = WordSearchPuzzle.create(words: FILEPATH)
9
+
10
+ # 2. Check if exits solution
11
+ if puzzle.nil?
12
+ puts "I'm sorry! Can't create the puzzle!"
13
+ puts "Revise filepath, please! (#{filename})"
14
+ exit 1
15
+ end
16
+
17
+ # 3. Show puzzle on screen
18
+ puts puzzle.render
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/word-search-puzzle"
3
+
4
+ # 1. Define your word list
5
+ words = %w[STARWARS OBIWAN LUKE VADER R2D2 C3PO LEIA YODA EMPERADOR SITH]
6
+
7
+ # 2. Create your puzzle (10x20 size)
8
+ puzzle = WordSearchPuzzle.create(
9
+ words: words,
10
+ size: "10x20"
11
+ )
12
+
13
+ # 3. Show puzzle on screen:
14
+ # - color: Use colors to highlight the words
15
+ # - padding: define the characters set used to fill the grid
16
+ puts puzzle.render(color: true, padding: ["."])
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/word-search-puzzle"
3
+
4
+ # 1. First desing your own grid shape and create the gaps
5
+ #
6
+ # 9x9 Grid example
7
+ #
8
+ # 0 1 2 3 4 5 6 7 8
9
+ # 0 | . @ @ . . . @ @ .
10
+ # 1 | @ @ @ @ . @ @ @ @
11
+ # 2 | @ @ @ @ @ @ @ @ @
12
+ # 3 | @ @ @ @ @ @ @ @ @
13
+ # 4 | . @ @ @ @ @ @ @ .
14
+ # 5 | . . @ @ @ @ @ . .
15
+ # 6 | . . . @ @ @ . . .
16
+ # 7 | . . . . @ . . . .
17
+ # 8 | . . . . . . . . .
18
+ #
19
+ # gaps:
20
+ # - Define gaps within the grid (row, col).
21
+ # - Gaps are positions within the grid that cannot be used for the puzzle.
22
+ gaps = [
23
+ [0, 0], [0, 3], [0, 4], [0, 5], [0, 8],
24
+ [1, 4],
25
+ [4, 0], [4, 8],
26
+ [5, 0], [5, 1], [5, 7], [5, 8],
27
+ [6, 0], [6, 1], [6, 2], [6, 6], [6, 7], [6, 8],
28
+ [7, 0], [7, 1], [7, 2], [7, 3], [7, 5], [7, 6], [7, 7], [7, 8],
29
+ [8, 0], [8, 1], [8, 2], [8, 3], [8, 4], [8, 5], [8, 6], [8, 7], [8, 8]
30
+ ]
31
+
32
+ # 2. Define words list
33
+ words = %w[DEATH STAR RETURN JEDI]
34
+
35
+ # 3. Create a puzzle with custom words, size and gaps
36
+ puzzle = WordSearchPuzzle.create(
37
+ words: words,
38
+ size: 9,
39
+ gaps: gaps
40
+ )
41
+
42
+ # 4. Render puzzle on screen
43
+ puts puzzle.render(color: true, padding: ["@"])
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+ require_relative "../lib/word-search-puzzle"
3
+
4
+ # 1. Read a CSV file with your gaps definition.
5
+ # Gaps positions within the grid that cannot be used for the puzzle.
6
+ FILEPATH = File.join(File.dirname(__FILE__), "ball-gaps.csv")
7
+
8
+ # 2. Define words list
9
+ words = %w[DEATH STAR RETURN JEDI]
10
+
11
+ # 3. Create puzzle with custom words, default size (10x10), and custom gaps
12
+ puzzle = WordSearchPuzzle.create(
13
+ words: words,
14
+ gaps: FILEPATH
15
+ )
16
+
17
+ # 4. Render puzzle on screen
18
+ puts puzzle.render(color: true, padding: ["@"])
@@ -0,0 +1,48 @@
1
+ 0,0
2
+ 0,1
3
+ 0,2
4
+ 0,3
5
+ 0,4
6
+ 0,5
7
+ 0,6
8
+ 0,7
9
+ 0,8
10
+ 0,9
11
+ 1,0
12
+ 1,1
13
+ 1,2
14
+ 1,7
15
+ 1,8
16
+ 1,9
17
+ 2,0
18
+ 2,1
19
+ 2,8
20
+ 2,9
21
+ 3,0
22
+ 3,9
23
+ 4,0
24
+ 4,9
25
+ 5,0
26
+ 5,9
27
+ 6,0
28
+ 6,9
29
+ 7,0
30
+ 7,1
31
+ 7,8
32
+ 7,9
33
+ 8,0
34
+ 8,1
35
+ 8,2
36
+ 8,7
37
+ 8,8
38
+ 8,9
39
+ 9,0
40
+ 9,1
41
+ 9,2
42
+ 9,3
43
+ 9,4
44
+ 9,5
45
+ 9,6
46
+ 9,7
47
+ 9,8
48
+ 9,9
@@ -0,0 +1,10 @@
1
+ ..........
2
+ ...@@@@...
3
+ ..@@@@@@..
4
+ .@@@@@@@@.
5
+ .@@@@@@@@.
6
+ .@@@@@@@@.
7
+ .@@@@@@@@.
8
+ ..@@@@@@..
9
+ ...@@@@...
10
+ ..........
@@ -0,0 +1,10 @@
1
+ STARWARS
2
+ OBIWAN
3
+ LUKE
4
+ VADER
5
+ R2D2
6
+ C3PO
7
+ LEIA
8
+ YODA
9
+ EMPERADOR
10
+ SITH
@@ -0,0 +1,44 @@
1
+ module WordSearchPuzzle
2
+ class Cell
3
+ attr_reader :data, :count
4
+
5
+ def initialize
6
+ @count = 0
7
+ @data = :empty
8
+ end
9
+
10
+ def empty?
11
+ @data == :empty
12
+ end
13
+
14
+ def gap?
15
+ @data == :gap
16
+ end
17
+
18
+ def gap!
19
+ @data = :gap
20
+ end
21
+
22
+ def in_use?
23
+ @count > 0
24
+ end
25
+
26
+ def push(data)
27
+ if empty? || @data == data
28
+ @data = data
29
+ @count += 1
30
+ else
31
+ raise "Cell.push: new data(#{data}) is not equal to current data(#{@data})!"
32
+ end
33
+ end
34
+
35
+ def pop
36
+ raise "Cell.pop: No data!" unless in_use?
37
+
38
+ @count -= 1
39
+ current_data = @data
40
+ @data = :empty if @count.zero?
41
+ current_data
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,22 @@
1
+ require_relative "../../word-search-puzzle"
2
+
3
+ module WordSearchPuzzle
4
+ class UserActions
5
+ def create(options)
6
+ puzzle = WordSearchPuzzle.create(
7
+ words: options["words"],
8
+ size: options["size"],
9
+ gaps: options["gaps"]
10
+ )
11
+
12
+ if puzzle.nil?
13
+ warn "Unable to create puzzle!"
14
+ exit 1
15
+ end
16
+
17
+ color = options["color"] || false
18
+ padding = options["padding"] ? options["padding"].split(",") : ("A".."Z").to_a
19
+ puts puzzle.render(color: color, padding: padding)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require_relative "version"
5
+ require_relative "../word-search-puzzle"
6
+ require_relative "cli/user_actions"
7
+
8
+ ##
9
+ # Command Line User Interface
10
+ class CLI < Thor
11
+ map ["h", "-h", "--help"] => "help"
12
+
13
+ map ["v", "-v", "--version"] => "version"
14
+ desc "version", "Show the program version"
15
+ def version
16
+ puts "#{WordSearchPuzzle::NAME} version #{WordSearchPuzzle::VERSION}"
17
+ exit 0
18
+ end
19
+
20
+ map ["c", "-c", "--create"] => "create"
21
+ option :words, required: true
22
+ option :size
23
+ option :color, type: :boolean
24
+ option :padding
25
+ option :gaps
26
+ desc "create [--size=SIZE][--color]", "Create puzzle"
27
+ long_desc <<-LONGDESC
28
+
29
+ - [--words=LIST], comma-separated list of words or file name with the words
30
+
31
+ - [--size=SIZE], Grid size. Default value 10x10.
32
+ Examples 5:, sets rows=5 and cols=5.
33
+ Example 10x20: sets rows=10 and col=20.
34
+
35
+ - [--color], Indicates whether to display the output with color. Default value false
36
+
37
+ - [--padding=LIST], comma.separated list of characters to fill puzzle. Default value A-Z.
38
+
39
+ - [--gaps=FILEPATH], TEXT file with grid shape, or CSV file with gaps coordinates.
40
+
41
+ LONGDESC
42
+ def create
43
+ WordSearchPuzzle::UserActions.new.create(options)
44
+ end
45
+
46
+ def respond_to_missing?(_method_name)
47
+ true
48
+ end
49
+
50
+ ##
51
+ # Thor stop and show messages on screen on failure
52
+ def self.exit_on_failure?
53
+ true
54
+ end
55
+ end
@@ -0,0 +1,91 @@
1
+ require_relative "cell"
2
+ require "colorize"
3
+
4
+ module WordSearchPuzzle
5
+ class Grid
6
+ def self.directions
7
+ {
8
+ n: {row: -1, col: 0},
9
+ ne: {row: -1, col: 1},
10
+ e: {row: 0, col: 1},
11
+ se: {row: 1, col: 1},
12
+ s: {row: 1, col: 0},
13
+ sw: {row: 1, col: -1},
14
+ w: {row: 0, col: -1},
15
+ nw: {row: -1, col: -1}
16
+ }
17
+ end
18
+ Coord = Data.define(:row, :col, :data)
19
+ attr_reader :rows, :cols
20
+
21
+ def initialize(rows, cols, gaps = [])
22
+ @rows = rows
23
+ @cols = cols
24
+ @matrix = []
25
+ (0...rows).each do |row|
26
+ data = []
27
+ (0...cols).each { |col| data << Cell.new }
28
+ @matrix[row] = data
29
+ end
30
+ gaps.each { |x, y| @matrix[x][y].gap! }
31
+ end
32
+
33
+ def cell(row, col)
34
+ @matrix[row][col]
35
+ end
36
+
37
+ def find_sequence(word, first_row, first_col, direction)
38
+ step = Grid.directions[direction]
39
+ row = first_row
40
+ col = first_col
41
+ coords = []
42
+ word.chars.each do |letter|
43
+ return [] if row >= @rows || col >= @cols || row < 0 || col < 0
44
+
45
+ cell = @matrix[row][col]
46
+ if cell.empty? || cell.data == letter
47
+ coords << Coord.new(row: row, col: col, data: letter)
48
+ row += step[:row]
49
+ col += step[:col]
50
+ else
51
+ return []
52
+ end
53
+ end
54
+ coords
55
+ end
56
+
57
+ def set_sequence(coords)
58
+ coords.each { @matrix[it.row][it.col].push(it.data) }
59
+ end
60
+
61
+ def unset_sequence(coords)
62
+ coords.each { @matrix[it.row][it.col].pop }
63
+ end
64
+
65
+ def render(color: false, padding: :default)
66
+ padding ||= ["."]
67
+ padding = ("A".."Z").to_a if padding == :default
68
+
69
+ lines = @matrix.map do |row|
70
+ row.map { render_cell(it, color, padding) }.join
71
+ end
72
+ lines.join("\n")
73
+ end
74
+
75
+ private
76
+
77
+ def render_cell(cell, color, padding)
78
+ return " " if cell.gap?
79
+ data = cell.data.to_s
80
+ data = padding.sample if cell.count.zero?
81
+
82
+ if color && cell.count.zero?
83
+ data = data.colorize(:gray)
84
+ elsif color
85
+ data = data.colorize(:ligth_white)
86
+ end
87
+
88
+ " " + data
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,96 @@
1
+ module WordSearchPuzzle
2
+ class Input
3
+ DEFAULT_GRID_SIZE = 10
4
+
5
+ def self.read_words(words)
6
+ if words.is_a? Array
7
+ if words.empty?
8
+ warn "Input: empty parameter! (words)"
9
+ exit 1
10
+ end
11
+ return words
12
+ elsif File.exist?(words)
13
+ lines = File.readlines(words)
14
+ lines.map! { it.strip }
15
+ lines.delete("")
16
+ if lines.empty?
17
+ warn "Input: empty file! (#{words})!"
18
+ exit 1
19
+ end
20
+ return lines
21
+ end
22
+
23
+ if words.nil? || words.empty?
24
+ warn "Input: empty parameter! (words)"
25
+ exit 1
26
+ end
27
+
28
+ words.split(",").map { it.strip }
29
+ end
30
+
31
+ def self.read_size(size)
32
+ if size.nil?
33
+ rows = cols = DEFAULT_GRID_SIZE
34
+ elsif size.is_a? Integer
35
+ rows = cols = size
36
+ else
37
+ values = size.split("x")
38
+ rows = values[0].to_i
39
+ rows = (rows < 1) ? DEFAULT_GRID_SIZE : rows
40
+ cols = (values.size == 2) ? values[1].to_i : rows
41
+ end
42
+ [rows, cols]
43
+ end
44
+
45
+ def self.read_gaps(gaps)
46
+ return gaps if gaps.is_a? Array
47
+ return [] if gaps.nil?
48
+
49
+ if File.exist?(gaps)
50
+ lines = File.readlines(gaps)
51
+ lines.delete("")
52
+
53
+ if gaps.end_with?(".csv")
54
+ data = lines.map do |line|
55
+ items = line.split(",")
56
+ [items[0].to_i, items[1].to_i]
57
+ end
58
+ else
59
+ data = []
60
+ lines.each_with_index do |line, row|
61
+ line.chars.each_with_index do |char, col|
62
+ data << [row, col] if char == "."
63
+ end
64
+ end
65
+ end
66
+ return data
67
+ end
68
+
69
+ []
70
+ end
71
+
72
+ def self.validate(words, rows, cols, gaps)
73
+ msg = []
74
+
75
+ words.each do |word|
76
+ msg << "E01: The word <#{word}> does not fit in the grid." if word.length > [rows, cols].max
77
+ end
78
+
79
+ if words.size == 1 && msg.size == 1
80
+ msg << "E02: If <#{words.first}> is a filepath then it was not found."
81
+ end
82
+
83
+ total_words_size = words.sum { it.length }
84
+ if total_words_size > (rows * cols - gaps.length)
85
+ msg << "E03: The grid is not large enough to contain all the words."
86
+ end
87
+
88
+ gaps.each do |y, x|
89
+ if y >= rows || x >= cols
90
+ msg << "E04: Gap [#{y}, #{x}] outside the grid size."
91
+ end
92
+ end
93
+ msg
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,63 @@
1
+ require_relative "grid"
2
+
3
+ module WordSearchPuzzle
4
+ class Strategy
5
+ def initialize(words, grid)
6
+ # Sort words from longest to smallest
7
+ @words = words.sort_by { it.length }.reverse
8
+ @initial_grid = grid
9
+ @final_grid = nil
10
+ end
11
+
12
+ def grid
13
+ @final_grid
14
+ end
15
+
16
+ def calculate
17
+ @final_grid = find_solution(@words.clone, @initial_grid.clone)
18
+ end
19
+
20
+ private
21
+
22
+ def find_solution(words, grid)
23
+ # Take the first word
24
+ word = words.delete_at(0)
25
+ available_word_locations = get_available_locations(word, grid)
26
+ return nil if available_word_locations.empty? # No solution
27
+
28
+ available_word_locations.shuffle!
29
+ available_word_locations.each do |sequence|
30
+ grid.set_sequence(sequence) # Push word into grid location
31
+ return grid if words.empty? # Solved puzzle. No more words to push
32
+
33
+ resolved_grid = find_solution(words.clone, grid.clone) # Find solution recursively
34
+ if resolved_grid
35
+ return resolved_grid # <new_grid> contains the solved puzzle
36
+ else
37
+ # The selected <sequence> of the word does not help to solve the puzzle.
38
+ # 1. Restore <grid> content
39
+ # 2. And try other sequence
40
+ grid.unset_sequence(sequence)
41
+ end
42
+ end
43
+ if words.empty?
44
+ grid # All the words has been placed
45
+ else
46
+ nil # All possibilities have been tried and there are still words left unplaced.
47
+ end
48
+ end
49
+
50
+ def get_available_locations(word, grid)
51
+ locations = []
52
+ (0...grid.rows).each do |row|
53
+ (0...grid.cols).each do |col|
54
+ Grid.directions.keys.each do |direction|
55
+ location = grid.find_sequence(word, row, col, direction)
56
+ locations << location unless location.empty?
57
+ end
58
+ end
59
+ end
60
+ locations
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WordSearchPuzzle
4
+ NAME = "word-search-puzzle"
5
+ VERSION = "0.1.0"
6
+ end
@@ -0,0 +1,26 @@
1
+ require_relative "word-search-puzzle/grid"
2
+ require_relative "word-search-puzzle/input"
3
+ require_relative "word-search-puzzle/strategy"
4
+
5
+ module WordSearchPuzzle
6
+ def self.create(words: [], size: "10x10", gaps: [])
7
+ # Read input values
8
+ words = WordSearchPuzzle::Input.read_words(words)
9
+ rows, cols = WordSearchPuzzle::Input.read_size(size)
10
+ gaps = WordSearchPuzzle::Input.read_gaps(gaps)
11
+
12
+ # Validate input values
13
+ errors = WordSearchPuzzle::Input.validate(words, rows, cols, gaps)
14
+ unless errors.empty?
15
+ puts "Unable to create puzzle:"
16
+ errors.each_with_index { |message, index| puts "#{index + 1}. #{message}" }
17
+ exit 1
18
+ end
19
+
20
+ # Create the grid ant try to find a puzzle
21
+ grid = Grid.new(rows, cols, gaps)
22
+ strategy = Strategy.new(words, grid)
23
+ strategy.calculate
24
+ strategy.grid
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+ class Cell
2
+ attr_reader data: Char
3
+ attr_reader count: Integer
4
+ end
@@ -0,0 +1,8 @@
1
+ module Word
2
+ module Search
3
+ module Puzzle
4
+ VERSION: String
5
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "lib/word-search-puzzle/cli"
4
+ require "debug"
5
+
6
+ # Start command line interface
7
+ CLI.start(ARGV)
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/word-search-puzzle/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "word-search-puzzle"
7
+ spec.version = WordSearchPuzzle::VERSION
8
+ spec.authors = ["David Vargas Ruiz"]
9
+ spec.email = ["dvarrui@proton.me"]
10
+
11
+ spec.summary = "Create word search puzzles"
12
+ # spec.description = "TODO: Write a longer description or delete this line."
13
+ spec.homepage = "https://github.com/dvarrui/word-search-puzzle"
14
+ spec.required_ruby_version = ">= 3.4.0"
15
+
16
+ # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
17
+
18
+ spec.metadata["homepage_uri"] = spec.homepage
19
+ spec.metadata["source_code_uri"] = spec.homepage
20
+ spec.metadata["changelog_uri"] = "https://github.com/dvarrui/word-search-puzzle/blob/main/CHANGELOG.md"
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(__dir__) do
25
+ `git ls-files -z`.split("\x0").reject do |f|
26
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
27
+ end
28
+ end
29
+ spec.bindir = "exe"
30
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.add_dependency "colorize", "~> 1.1.0"
34
+ spec.add_dependency "thor", "~> 1.4.0"
35
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: word-search-puzzle
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Vargas Ruiz
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: colorize
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 1.1.0
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: 1.1.0
26
+ - !ruby/object:Gem::Dependency
27
+ name: thor
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 1.4.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 1.4.0
40
+ email:
41
+ - dvarrui@proton.me
42
+ executables: []
43
+ extensions: []
44
+ extra_rdoc_files: []
45
+ files:
46
+ - ".ruby-version"
47
+ - ".standard.yml"
48
+ - CHANGELOG.md
49
+ - Gemfile
50
+ - Gemfile.lock
51
+ - README.md
52
+ - Rakefile
53
+ - docs/development.md
54
+ - docs/images/color-option.png
55
+ - docs/images/puzzle-01.png
56
+ - docs/images/puzzle-03.png
57
+ - docs/options.md
58
+ - examples/01-default.rb
59
+ - examples/02-read_words_file.rb
60
+ - examples/03-render_options.rb
61
+ - examples/04-custom_shape.rb
62
+ - examples/05-read_gaps_file.rb
63
+ - examples/ball-gaps.csv
64
+ - examples/ball-shape.txt
65
+ - examples/starwars.txt
66
+ - lib/word-search-puzzle.rb
67
+ - lib/word-search-puzzle/cell.rb
68
+ - lib/word-search-puzzle/cli.rb
69
+ - lib/word-search-puzzle/cli/user_actions.rb
70
+ - lib/word-search-puzzle/grid.rb
71
+ - lib/word-search-puzzle/input.rb
72
+ - lib/word-search-puzzle/strategy.rb
73
+ - lib/word-search-puzzle/version.rb
74
+ - sig/word-search-puzzle/cell.rbs
75
+ - sig/word-search-puzzle/version.rbs
76
+ - word-search-puzzle
77
+ - word-search-puzzle.gemspec
78
+ homepage: https://github.com/dvarrui/word-search-puzzle
79
+ licenses: []
80
+ metadata:
81
+ homepage_uri: https://github.com/dvarrui/word-search-puzzle
82
+ source_code_uri: https://github.com/dvarrui/word-search-puzzle
83
+ changelog_uri: https://github.com/dvarrui/word-search-puzzle/blob/main/CHANGELOG.md
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: 3.4.0
92
+ required_rubygems_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubygems_version: 3.7.2
99
+ specification_version: 4
100
+ summary: Create word search puzzles
101
+ test_files: []