crucigrama 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +17 -0
- data/Gemfile.lock +50 -0
- data/LICENSE.txt +7 -0
- data/README.rdoc +25 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/bin/crucigrama +11 -0
- data/examples/crosswords/01.json +1 -0
- data/examples/crosswords/02.json +1 -0
- data/examples/crosswords/03.json +1 -0
- data/examples/lemaries/es-1.txt +85098 -0
- data/features/crucigrama.feature +9 -0
- data/features/step_definitions/crucigrama_steps.rb +0 -0
- data/features/support/env.rb +13 -0
- data/lib/crucigrama/cli/generate.rb +106 -0
- data/lib/crucigrama/cli/print.rb +70 -0
- data/lib/crucigrama/cli.rb +39 -0
- data/lib/crucigrama/crossword/definitions.rb +61 -0
- data/lib/crucigrama/crossword/grid.rb +106 -0
- data/lib/crucigrama/crossword/line_query.rb +16 -0
- data/lib/crucigrama/crossword/pdf_printable.rb +152 -0
- data/lib/crucigrama/crossword/serializable.rb +31 -0
- data/lib/crucigrama/crossword/word_query.rb +107 -0
- data/lib/crucigrama/crossword.rb +38 -0
- data/lib/crucigrama/crossword_builder.rb +110 -0
- data/lib/crucigrama/grill_builder.rb +51 -0
- data/lib/crucigrama/positionable.rb +19 -0
- data/lib/crucigrama/version.rb +15 -0
- data/lib/crucigrama.rb +5 -0
- data/spec/crucigrama_spec.rb +7 -0
- data/spec/spec_helper.rb +29 -0
- metadata +192 -0
File without changes
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
begin
|
3
|
+
Bundler.setup(:default, :development)
|
4
|
+
rescue Bundler::BundlerError => e
|
5
|
+
$stderr.puts e.message
|
6
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
7
|
+
exit e.status_code
|
8
|
+
end
|
9
|
+
|
10
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
11
|
+
require 'crucigrama'
|
12
|
+
|
13
|
+
require 'rspec/expectations'
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'crucigrama/cli'
|
2
|
+
require 'optparse'
|
3
|
+
class Crucigrama::CLI::Generate
|
4
|
+
Crucigrama::CLI.register 'generate', self, "Generates a crossword"
|
5
|
+
|
6
|
+
def options_parser
|
7
|
+
@options_parser ||= OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: #{Crucigrama::CLI.cli_command} generate options"
|
9
|
+
opts.separator ""
|
10
|
+
opts.separator "Options:"
|
11
|
+
opts.on("-l", "--lemary LEMARY", "Uses LEMARY as words source") do |lemary|
|
12
|
+
begin
|
13
|
+
@options[:lemary] = File.read(lemary).split("\n")
|
14
|
+
rescue Exception => exc
|
15
|
+
STDERR.puts exc.message
|
16
|
+
end
|
17
|
+
end
|
18
|
+
opts.on("-d", "--dimensions DIMENSIONS", "Generates crossword of DIMENSIONS size",
|
19
|
+
"in nxn format (10x10 by default)") do |dimensions|
|
20
|
+
unless match = /(\d)+x(\d)+/.match(dimensions)
|
21
|
+
STDERR.puts "Incorrect dimensions format, it must be nxn (for example 10x11)"
|
22
|
+
exit 1
|
23
|
+
end
|
24
|
+
@options[:dimensions] = Hash[[:horizontal, :vertical].collect.with_index do |direction, i|
|
25
|
+
[direction, match[i].to_i]
|
26
|
+
end]
|
27
|
+
end
|
28
|
+
opts.on("-o", "--output FILE", "Dumps the generated crossword to FILE") do |file|
|
29
|
+
@options[:file] = file
|
30
|
+
end
|
31
|
+
opts.on("--[no-]definitions", "Asks for definitions for the generated crossword") do |definitions|
|
32
|
+
@options[:definitions] = definitions
|
33
|
+
end
|
34
|
+
opts.on("--[no-]reverse-words", "Allow reverse words for the generated crossword") do |value|
|
35
|
+
@options[:allow_reverse] = value
|
36
|
+
end
|
37
|
+
opts.separator ""
|
38
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
39
|
+
puts opts
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
opts.on_tail("--version", "Show version") do
|
43
|
+
puts Crucigrama::VERSION
|
44
|
+
exit
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def extract_options(*args)
|
50
|
+
@options = {}
|
51
|
+
options_parser.parse!(args)
|
52
|
+
unless options[:lemary]
|
53
|
+
STDERR.puts "Must specify lemary file!"
|
54
|
+
exit 1
|
55
|
+
end
|
56
|
+
if @options[:allow_reverse]
|
57
|
+
@options[:lemary] = @options[:lemary].collect{|word| [word, word.reverse]}.flatten.uniq
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(*args)
|
62
|
+
extract_options(*args)
|
63
|
+
crossword = generate_crossword
|
64
|
+
dump_crossword(crossword)
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
attr_reader :options
|
70
|
+
|
71
|
+
def generate_crossword
|
72
|
+
crossword = Crucigrama::GrillBuilder.build_crossword(:valid_words => options[:lemary], :dimensions => options[:dimensions])
|
73
|
+
STDERR.puts "Crossword generated with #{crossword.black_positions.count} black cells (#{100*crossword.black_positions.count.to_f/crossword.dimensions.values.inject(1,&:*)}%)"
|
74
|
+
if options[:definitions]
|
75
|
+
fill_with_definitions(crossword)
|
76
|
+
end
|
77
|
+
crossword
|
78
|
+
end
|
79
|
+
|
80
|
+
def dump_crossword(crossword)
|
81
|
+
if options[:file]
|
82
|
+
begin
|
83
|
+
File.open(options[:file],'w'){|file| file.write(crossword.to_json)}
|
84
|
+
rescue Exception => exc
|
85
|
+
STDERR.puts exc.message
|
86
|
+
exit 1
|
87
|
+
end
|
88
|
+
else
|
89
|
+
puts crossword.to_json
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def fill_with_definitions(crossword)
|
94
|
+
positions = Hash[ [:horizontal, :vertical].collect do |direction|
|
95
|
+
[direction, crossword.word_positions.keys.select{|w| w.length > 1 and crossword.word_positions[w][direction] }.collect{|w| crossword.word_positions[w][direction]}.flatten(1)]
|
96
|
+
end]
|
97
|
+
positions.each do |direction, word_positions|
|
98
|
+
STDERR.puts "#{direction.to_s.capitalize} definitions:"
|
99
|
+
word_positions.each do |position|
|
100
|
+
STDERR.puts "[#{position[:horizontal]+1}, #{position[:vertical]+1}] #{crossword.word_at(position, direction)}:"
|
101
|
+
crossword.add_definition(STDIN.readline.strip, position, direction)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'crucigrama/cli'
|
2
|
+
require 'optparse'
|
3
|
+
class Crucigrama::CLI::Print
|
4
|
+
Crucigrama::CLI.register 'print', self, "Prints a crossword to a PDF file"
|
5
|
+
|
6
|
+
def options_parser
|
7
|
+
@options_parser ||= OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: #{Crucigrama::CLI.cli_command} print options"
|
9
|
+
opts.separator ""
|
10
|
+
opts.separator "Options:"
|
11
|
+
opts.on("-o", "--output PDF_FILE", "Prints the crossword to PDF_FILE") do |file|
|
12
|
+
@options[:output_file] = file
|
13
|
+
end
|
14
|
+
opts.on("-i", "--input JSON_FILE", "Extracts the crossword from the JSON_FILE file") do |file|
|
15
|
+
begin
|
16
|
+
@options[:json_crossword] = File.read(file)
|
17
|
+
rescue Exception => exc
|
18
|
+
STDERR.puts exc.message
|
19
|
+
end
|
20
|
+
end
|
21
|
+
opts.on("--[no-]solution", "Prints a reduced and inverted solution for the crossword next to it") do |value|
|
22
|
+
@options[:include_solution] = value
|
23
|
+
end
|
24
|
+
opts.separator ""
|
25
|
+
opts.on_tail("-h", "--help", "Show this message") do
|
26
|
+
puts opts
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
opts.on_tail("--version", "Show version") do
|
30
|
+
puts Crucigrama::VERSION
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def extract_options(*args)
|
37
|
+
@options = {}
|
38
|
+
options_parser.parse!(args)
|
39
|
+
unless options[:json_crossword]
|
40
|
+
STDERR.puts "Must specify input file!"
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
unless options[:output_file]
|
44
|
+
STDERR.puts "Must specify output file!"
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(*args)
|
50
|
+
extract_options(*args)
|
51
|
+
crossword = begin
|
52
|
+
Crucigrama::Crossword.new(MultiJson.decode(options[:json_crossword]))
|
53
|
+
rescue Exception => exc
|
54
|
+
STDERR.puts exc.message
|
55
|
+
STDERR.puts "Could not parse input crossword!"
|
56
|
+
exit 1
|
57
|
+
end
|
58
|
+
begin
|
59
|
+
crossword.to_pdf(options[:output_file], :include_solution => options[:include_solution])
|
60
|
+
rescue Exception => exc
|
61
|
+
STDERR.puts exc.message
|
62
|
+
STDERR.puts "Could not print crossword!"
|
63
|
+
exit 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
attr_reader :options
|
70
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'crucigrama'
|
2
|
+
|
3
|
+
module Crucigrama::CLI
|
4
|
+
@@commands = {}
|
5
|
+
@@cli_command = nil
|
6
|
+
|
7
|
+
def self.cli_command
|
8
|
+
@@cli_command
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.cli_command=(value)
|
12
|
+
@@cli_command = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.register(command, command_handler, description)
|
16
|
+
@@commands[command] = {:command_handler => command_handler, :description => description}
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.deregister(command)
|
20
|
+
@@commands.delete(command)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.run(*args)
|
24
|
+
command = args.delete_at(0)
|
25
|
+
if command = @@commands[command]
|
26
|
+
command[:command_handler].new(*args)
|
27
|
+
else
|
28
|
+
show_banner
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.show_banner
|
33
|
+
puts "\nUsage: #{self.cli_command} command [options]\n
|
34
|
+
where command can be one of the following:\n
|
35
|
+
#{@@commands.collect do |key, value|
|
36
|
+
"#{key}: #{value[:description]}"
|
37
|
+
end.join("\n")}\n\n"
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# This module provides the behaviour related to crosswords' definitions
|
2
|
+
module Crucigrama::Crossword::Definitions
|
3
|
+
|
4
|
+
# Initializes the definitions of a crossword if given
|
5
|
+
# @param [Hash<Symbol,Integer>, optional] options the options of the crossword
|
6
|
+
# @option options [Hash] :definitions the definitions for the word in the crossword as returned by method #definitions
|
7
|
+
def initialize(options={})
|
8
|
+
super(options)
|
9
|
+
self.definitions = (options[:definitions]||{})
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Hash<Symbol,Hash<Integer,Hash<Integer,String>>>] the definitions in the crossword, in a hash to be accessed
|
13
|
+
# with keys direction (:horizontal or :vertical), the other direction coordinate and the given direction coordinate
|
14
|
+
def definitions
|
15
|
+
@definitions
|
16
|
+
end
|
17
|
+
|
18
|
+
# Sets the definitions for the crossword words
|
19
|
+
# @param [Hash<String|Symbol,Hash<String|Integer,Hash<String|Integer,String>>>] defs the definitions
|
20
|
+
def definitions=(defs)
|
21
|
+
@definitions = Hash[defs.collect do |key,value|
|
22
|
+
[key.to_sym, Hash[value.collect do |key, val|
|
23
|
+
[key.to_i, Hash[val.collect{|k,v| [k.to_i, v]}]]
|
24
|
+
end]]
|
25
|
+
end]
|
26
|
+
end
|
27
|
+
|
28
|
+
# Adds a crossword definition for the given position and direction
|
29
|
+
# @param [String] definition the definition to add
|
30
|
+
# @param [Hash<Symbol, Integer>] position a hash with the horizontal and vertical
|
31
|
+
# coordinates of the cell where the defined word starts
|
32
|
+
# @param [Symbol] direction the direction of the defined word, :horizontal or :vertical
|
33
|
+
def add_definition(definition, position, direction)
|
34
|
+
definitions[direction]||={}
|
35
|
+
definitions[direction][position[direction_other_than(direction)]]||={}
|
36
|
+
definitions[direction][position[direction_other_than(direction)]][position[direction]] = definition
|
37
|
+
end
|
38
|
+
|
39
|
+
# Removes a crossword definition for the given position and direction
|
40
|
+
# @return [String,nil] the removed definition if found, nil otherwise
|
41
|
+
# @param [Hash<Symbol, Integer>] position a hash with the horizontal and vertical
|
42
|
+
# coordinates of the cell where the word whose definition is to be removed starts
|
43
|
+
# @param [Symbol] direction the direction of the word whose definition is to be removed,
|
44
|
+
# :horizontal or :vertical
|
45
|
+
def remove_definition(position, direction)
|
46
|
+
if definitions[direction] and definitions[direction][position[direction_other_than(direction)]]
|
47
|
+
definition = definitions[direction][position[direction_other_than(direction)]].delete(position[direction])
|
48
|
+
definitions[direction].delete(position[direction_other_than(direction)]) if definitions[direction][position[direction_other_than(direction)]].empty?
|
49
|
+
definition
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [String,nil] the definition for the word at the given position and direction
|
54
|
+
# @param [Hash<Symbol, Integer>] position a hash with the horizontal and vertical
|
55
|
+
# coordinates of the cell where the queried word starts
|
56
|
+
# @param [Symbol] direction the direction of the queried word, :horizontal or :vertical
|
57
|
+
def definition_for(position, direction)
|
58
|
+
return nil unless definitions[direction] and definitions[direction][position[direction_other_than(direction)]]
|
59
|
+
definitions[direction][position[direction_other_than(direction)]][position[direction]]
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# This module implements the behaviour associated to the crossword grid
|
2
|
+
module Crucigrama::Crossword::Grid
|
3
|
+
|
4
|
+
# Initializes an empty crossword (without words) of the given dimensions
|
5
|
+
# @param [Hash<Symbol,Integer>, optional] options the dimensions of the crossword
|
6
|
+
# @option options [Integer] :horizontal the horizontal size of the crossword, that is, how many columns it has
|
7
|
+
# @option options [Integer] :vertical the vertical size of the crossword, that is, how many rows it has
|
8
|
+
# @option options [Hash] :dimensions a Hash with the :horizontal and :vertical options described above
|
9
|
+
# @option options [String] :grid a raw representation of the crossword grid as described in #grid
|
10
|
+
# @option options [Hash] :definitions the definitions for the word in the crossword as returned by method #definitions
|
11
|
+
# @todo :dimensions option must be able to accept [x,y] representation according to Crucigrama::Positionable#position
|
12
|
+
# @todo if accept options as array of two integers [x,y]
|
13
|
+
def initialize(options={})
|
14
|
+
super(options)
|
15
|
+
options.to_options!
|
16
|
+
@grid = {}
|
17
|
+
if options[:grid]
|
18
|
+
self.grid = options[:grid]
|
19
|
+
else
|
20
|
+
dimensions = {:horizontal => 10, :vertical => 10}.merge(options[:dimensions]||options)
|
21
|
+
@grid[:horizontal] = Array.new(dimensions[:vertical].to_i){ Array.new(dimensions[:horizontal].to_i){self.class::BLACK}}
|
22
|
+
@grid[:vertical] = Array.new(dimensions[:horizontal].to_i){ Array.new(dimensions[:vertical].to_i){self.class::BLACK}}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Builds the crossword grid specified
|
27
|
+
# @param [String] grid a raw representation for the crossword grid to build
|
28
|
+
def grid=(grid)
|
29
|
+
@grid[:horizontal] = grid.split("\n").collect do |row|
|
30
|
+
row.chars.to_a
|
31
|
+
end
|
32
|
+
@grid[:vertical] = @grid[:horizontal].collect.with_index do |row, i|
|
33
|
+
row.collect.with_index do |char, j|
|
34
|
+
@grid[:horizontal][j][i]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
grid_modified!
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [String] a raw representation for the crossword grid
|
41
|
+
def grid
|
42
|
+
@grid[:horizontal].collect do |row|
|
43
|
+
"#{row.join}\n"
|
44
|
+
end.join
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# It adds a word to the crossword setting it on the given coordinates and direction, if it can be set.
|
49
|
+
# @param [String] word the word to add to the crossword
|
50
|
+
# @param [Hash<Symbol,Integer>] coordinates the coordinates of the crossword cell where the word must start
|
51
|
+
# @option coordinates [Integer] :horizontal a number between 0 and the horizontal dimension of the
|
52
|
+
# crossword minus one specifying the row of the cell where the word must start
|
53
|
+
# @option coordinates [Integer] :vertical a number between 0 and the vertical dimension of the
|
54
|
+
# crossword minus one specifying the column of the cell where the word must start
|
55
|
+
# @param [:horizontal, :vertical] direction the direction for the word to be set on the crossword
|
56
|
+
# @return [Boolean] if the word can be set
|
57
|
+
def add(word, coordinates, direction)
|
58
|
+
constant_coordinate = direction_other_than(direction)
|
59
|
+
word.chars.with_index do |char, word_position|
|
60
|
+
@grid[direction][coordinates[constant_coordinate]][coordinates[direction]+word_position] = char
|
61
|
+
@grid[constant_coordinate][coordinates[direction]+word_position][coordinates[constant_coordinate]] = char
|
62
|
+
end
|
63
|
+
grid_modified!
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [Hash<Symbol,Integer>] the horizontal and vertical dimensions of the crossword
|
68
|
+
def dimensions
|
69
|
+
@dimensions ||={:horizontal =>@grid[:vertical].count, :vertical => @grid[:horizontal].count}
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [String] the char in the crossword at the given coordinates
|
73
|
+
# @param [Hash<Symbol,Integer>] coordinates the coordinates for the crossword cell being queried
|
74
|
+
# @option coordinates [Integer] :horizontal a number between 0 and the horizontal dimension of the
|
75
|
+
# crossword minus one specifying the row of the cell being queried
|
76
|
+
# @option coordinates [Integer] :vertical a number between 0 and the vertical dimension of the
|
77
|
+
# crossword minus one specifying the column of the cell being queried
|
78
|
+
def char_at(coordinates)
|
79
|
+
@grid[:horizontal][coordinates[:vertical]][coordinates[:horizontal]]
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [String] the line in the crossword in the given direction
|
83
|
+
# (row for :horizontal, column for :vertical) with the specified number
|
84
|
+
# @param [Integer] coordinate a number between 0 and the horizontal dimension of the
|
85
|
+
# crossword minus one specifying the queried line
|
86
|
+
# @param [:horizontal, :vertical] direction the direction of the line being queried
|
87
|
+
def line(coordinate, direction)
|
88
|
+
@grid[direction][coordinate].join
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Array<String>] an array of lines in the given direction, that is, an array with the results
|
92
|
+
# of calling {line(coordinate, direction)} on every line in the given dimension
|
93
|
+
# @param [:horizontal, :vertical] direction the direction of the lines being queried
|
94
|
+
def lines(direction)
|
95
|
+
@grid[direction].collect(&:join)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# It notifies the crossword that its internal grid changed, so that cached structure information may be discarded.
|
101
|
+
# Every module included after this should provide an implementation that
|
102
|
+
# * Discards information depending on the grid
|
103
|
+
# * Calls super to allow the execution of other modules implementations of this method
|
104
|
+
def grid_modified!
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#This module provides methods for querying lines (rows or columns) on a crossword
|
2
|
+
module Crucigrama::Crossword::LineQuery
|
3
|
+
# @return [String] the row with the specified number
|
4
|
+
# @param [Integer] coordinate a number between 0 and the horizontal dimension of the
|
5
|
+
# crossword minus one specifying the queried row
|
6
|
+
def row(coordinate)
|
7
|
+
line(coordinate, :horizontal)
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [String] the column with the specified number
|
11
|
+
# @param [Integer] coordinate a number between 0 and the vertical dimension of the
|
12
|
+
# crossword minus one specifying the queried column
|
13
|
+
def column(coordinate)
|
14
|
+
line(coordinate, :vertical)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'prawn'
|
2
|
+
|
3
|
+
|
4
|
+
# This module provides a to_pdf method that allows the printing of a crossword in PDF format
|
5
|
+
module Crucigrama::Crossword::PdfPrintable
|
6
|
+
|
7
|
+
# @return [Integer] the size in PDF points of a cell side
|
8
|
+
PDF_CELL_SIZE = 20
|
9
|
+
|
10
|
+
# Prints the crossword on the given file in PDF format
|
11
|
+
# @param [String, Prawn::Document] file the name of the file (if a String) or the PDF document
|
12
|
+
# (if a Prawn::Document) where the crossword must be printed
|
13
|
+
# @params [Hash<Symbol,Object>] options a set of options governing the PDF printing:
|
14
|
+
# @option options [Boolean] :include_solution whether or not to print a reduced and inverted
|
15
|
+
# solved crossword next to the actual one (false by default)
|
16
|
+
def to_pdf(file, options = {})
|
17
|
+
drawing_block = Proc.new do |pdf|
|
18
|
+
draw_crossword(pdf, :solved => false, :indexes => true)
|
19
|
+
pdf.move_down PDF_CELL_SIZE
|
20
|
+
if options[:include_solution]
|
21
|
+
cursor = pdf.cursor
|
22
|
+
draw_crossword(pdf, :solved => true,
|
23
|
+
:at => [pdf.bounds.width - PDF_CELL_SIZE*(dimensions[:horizontal])/2,
|
24
|
+
pdf.cursor + PDF_CELL_SIZE*(dimensions[:vertical])/2],
|
25
|
+
:scale => 0.5,
|
26
|
+
:inverted => true)
|
27
|
+
|
28
|
+
pdf.move_cursor_to cursor #- PDF_CELL_SIZE
|
29
|
+
end
|
30
|
+
pdf.move_down PDF_CELL_SIZE
|
31
|
+
draw_definitions(pdf)
|
32
|
+
end
|
33
|
+
if file.is_a?(Prawn::Document)
|
34
|
+
drawing_block.call(file)
|
35
|
+
else
|
36
|
+
Prawn::Document.generate(file, &drawing_block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# Draws the crossword grid on the given Prawn::Document
|
43
|
+
# @param [Prawn::Document] pdf the pdf on which to print the crossword grid
|
44
|
+
# @param [Hash] options a set of options governing the crossword grid printing
|
45
|
+
# @option options [Boolean] :indexes whether or not to draw column and row indexes (false by default)
|
46
|
+
# @option options [Boolean] :inverted whether or not to draw the grid inverted (false by default)
|
47
|
+
# @option options [Float] :scale a scaling factor for the crossword grid, to reduce or increase its size.
|
48
|
+
# The value by default, 1, will print it with cell sizes of {{Crucigrama::Crossword::PdfPrintable::PDF_CELL_SIZE}}
|
49
|
+
# @option options [Array<Integer>] :at a 2-dimensional array indicating horizontal and vertical document coordinates
|
50
|
+
# where the upper left corner of the grid will be placed
|
51
|
+
# @option options [Boolean] :solved whether or not to show the content for non-black crossword cells
|
52
|
+
# grid cells (false by default)
|
53
|
+
def draw_crossword(pdf, options={})
|
54
|
+
space_for_indexes = options[:indexes] ? 1 : 0
|
55
|
+
options = options.merge(:height => PDF_CELL_SIZE*(dimensions[:vertical] + space_for_indexes),
|
56
|
+
:width => PDF_CELL_SIZE*(dimensions[:horizontal] + space_for_indexes),
|
57
|
+
:rotation => options[:inverted] ? 180 : 0)
|
58
|
+
options = { :at => [0, pdf.cursor],
|
59
|
+
:scale => 1}.merge(options)
|
60
|
+
crossword_center = [options[:at][0]+options[:width]/2, options[:at][1]-options[:height]/2]
|
61
|
+
|
62
|
+
# pdf.stroke_axis
|
63
|
+
pdf.scale(options[:scale], :origin => options[:at]) do
|
64
|
+
pdf.rotate(options[:rotation], :origin => crossword_center) do
|
65
|
+
pdf.bounding_box options[:at], options do
|
66
|
+
dimensions[:horizontal].times do |x|
|
67
|
+
dimensions[:vertical].times do |y|
|
68
|
+
draw_cell(pdf,x,y,options)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
if options[:indexes]
|
72
|
+
draw_indexes(pdf,options)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Draws the crossword grid cell corresponding to the given coordinates on the given Prawn::Document
|
80
|
+
# @param [Prawn::Document] pdf the pdf on which to print the crossword grid cell
|
81
|
+
# @param [Integer] x The horizontal coordinate for the crossword cell to be printed
|
82
|
+
# @param [Integer] y The vertical coordinate for the crossword cell to be printed
|
83
|
+
# @param [Hash] options a set of options governing the crossword grid cell printing
|
84
|
+
# @option options [Boolean] :indexes whether or not column and row indexes are being drawn (false by default)
|
85
|
+
# @option options [Boolean] :solved whether or not to show the content for non-black crossword cells
|
86
|
+
# grid cells (false by default)
|
87
|
+
def draw_cell(pdf, x, y, options={})
|
88
|
+
index_space = options[:indexes] ? 1 : 0
|
89
|
+
graphic_y = dimensions[:vertical]- y - index_space
|
90
|
+
graphic_x = x + index_space
|
91
|
+
pdf.stroke_rectangle *pdf_cell_box(graphic_x, graphic_y)
|
92
|
+
cell_content = char_at(position(x,y))#@panel[:horizontal][y][x]
|
93
|
+
if cell_content == self.class::BLACK
|
94
|
+
pdf.fill_rectangle *pdf_cell_box(graphic_x, graphic_y)
|
95
|
+
elsif options[:solved]
|
96
|
+
pdf.text_box cell_content.upcase, :at => [graphic_x*PDF_CELL_SIZE, graphic_y*PDF_CELL_SIZE],
|
97
|
+
:height => PDF_CELL_SIZE,
|
98
|
+
:width => PDF_CELL_SIZE,
|
99
|
+
:align => :center,
|
100
|
+
:valign => :center
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Draws the crossword grid indexes for a crossword grid on the given Prawn::Document
|
105
|
+
# @param [Prawn::Document] pdf the pdf on which to print the crossword grid cell
|
106
|
+
# @param [Hash] options a set of options governing the crossword grid cell printing (not yet used)
|
107
|
+
def draw_indexes(pdf, options={})
|
108
|
+
dimensions[:horizontal].times do |x|
|
109
|
+
draw_index(pdf, x+1, dimensions[:vertical] , x+1)
|
110
|
+
end
|
111
|
+
dimensions[:vertical].times do |y|
|
112
|
+
draw_index(pdf, 0, dimensions[:vertical] - (y+1), y+1)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Draws an index for a column or row on the given Prawn::Document
|
117
|
+
# @param [Prawn::Document] pdf the pdf on which to print the crossword grid cell
|
118
|
+
# @param [Integer] x The horizontal coordinate in {{PDF_CELL_SIZE}} units indicating where to place the index
|
119
|
+
# @param [Integer] y The vertical coordinate in {{PDF_CELL_SIZE}} units indicating where to place the index
|
120
|
+
# @param [Integer] n The index to be drawn
|
121
|
+
def draw_index(pdf,x,y,n)
|
122
|
+
pdf.text_box "#{n}", :at => [x*PDF_CELL_SIZE, y*PDF_CELL_SIZE],
|
123
|
+
:height => PDF_CELL_SIZE,
|
124
|
+
:width => PDF_CELL_SIZE,
|
125
|
+
:align => :center,
|
126
|
+
:valign => :center
|
127
|
+
end
|
128
|
+
|
129
|
+
# Draws the definitions for a crossword on the given Prawn::Document
|
130
|
+
# @param [Prawn::Document] pdf the pdf on which to print the crossword grid cell
|
131
|
+
# @param [Hash] options a set of options governing the crossword definition printing
|
132
|
+
# @option options [Hash<Symbol, #to_s>] :headers Headers or titles for the horizontal and vertical definitions sections.
|
133
|
+
# "Horizontal" and "Vertical" by default
|
134
|
+
def draw_definitions(pdf, options={})
|
135
|
+
options = {:headers => {}}.merge(options)
|
136
|
+
cursor = pdf.cursor
|
137
|
+
definitions_text = [:horizontal, :vertical].collect do |direction|
|
138
|
+
"<b>#{(options[:headers][direction]||direction.to_s.capitalize)}</b>\n" +
|
139
|
+
Hash[(definitions[direction]||{}).sort].collect do |line, defs|
|
140
|
+
"<b>#{line+1}</b>.- #{defs.values.join(". ")}"
|
141
|
+
end.join(". ")
|
142
|
+
end.join(".\n\n")
|
143
|
+
pdf.text definitions_text, :inline_format => true
|
144
|
+
end
|
145
|
+
|
146
|
+
# @return [Array] A crossword cell box complying to {{Prawn::Document#fill_rectangle}} and {{Prawn::Document#stroke_rectangle}} interface
|
147
|
+
# @param [Integer] x the horizontal coordinate for the cell box in PDF_CELL_SIZE units
|
148
|
+
# @param [Integer] y the vertical coordinate for the cell box in PDF_CELL_SIZE units
|
149
|
+
def pdf_cell_box(x,y)
|
150
|
+
[[x*PDF_CELL_SIZE, y*PDF_CELL_SIZE], PDF_CELL_SIZE, PDF_CELL_SIZE]
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
# This module provides methods to serialize a crossword
|
4
|
+
module Crucigrama::Crossword::Serializable
|
5
|
+
|
6
|
+
# @return [Hash] a hash with the dimensions, grid and serialized definitions of a crossword
|
7
|
+
def attributes
|
8
|
+
{
|
9
|
+
:dimensions => dimensions,
|
10
|
+
:grid => grid,
|
11
|
+
:definitions => serialized_definitions
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Hash] the definitions of a crossword in a JSON-compatible format
|
16
|
+
def serialized_definitions
|
17
|
+
Hash[definitions.collect do |direction, defs|
|
18
|
+
[direction, Hash[defs.collect do |line_number, line_defs|
|
19
|
+
[line_number.to_s, Hash[line_defs.collect do |pos_number, definition|
|
20
|
+
[pos_number.to_s, definition]
|
21
|
+
end]]
|
22
|
+
end]]
|
23
|
+
end]
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [String] the attributes of the crossword as returned by #attributes in a JSON string
|
27
|
+
def to_json
|
28
|
+
MultiJson.encode(attributes)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|