crucigrama 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,9 @@
1
+ Feature: something something
2
+ In order to something something
3
+ A user something something
4
+ something something something
5
+
6
+ Scenario: something something
7
+ Given inspiration
8
+ When I create a sweet new gem
9
+ Then everyone should see how awesome I am
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