Created
September 15, 2023 17:42
-
-
Save kobaltz/8e811ce7965baf9b5e1685a1990cb9a5 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'prawn' | |
class SudokuPDF | |
GRID_SIZE = 9 | |
BOX_SIZE = 3 | |
THICK_LINE_WIDTH = 3 | |
THIN_LINE_WIDTH = 1 | |
NUM_PER_DIFFICULTY = 50 | |
def initialize | |
@document = Prawn::Document.new | |
@grid_size = 540 | |
@grid_cell_size = @grid_size / GRID_SIZE | |
end | |
def generate_multi_page_pdf | |
difficulties.each_with_index do |difficulty, index| | |
draw_difficulty_title(difficulty) | |
generate_and_draw_sudoku(difficulty) | |
@document.start_new_page unless last_difficulty?(index, difficulties) | |
end | |
@document.render_file("sudoku_puzzles.pdf") | |
end | |
private | |
def difficulties | |
[:easy] * NUM_PER_DIFFICULTY + [:medium] * NUM_PER_DIFFICULTY + [:hard] * NUM_PER_DIFFICULTY | |
end | |
def draw_difficulty_title(difficulty) | |
title_position = [grid_left, @document.bounds.top - 50] | |
@document.draw_text difficulty.to_s.capitalize, at: title_position, size: 30, style: :bold | |
end | |
def generate_and_draw_sudoku(difficulty) | |
generator = SudokuGenerator.new | |
generator.solve | |
generator.remove_numbers(difficulty) | |
draw_sudoku_grid(generator.display) | |
end | |
def draw_sudoku_grid(values) | |
set_default_stroke_color | |
draw_grid_lines | |
fill_cell_values(values) | |
end | |
def set_default_stroke_color | |
@document.stroke_color '000000' | |
end | |
def draw_grid_lines | |
(0..GRID_SIZE).each do |index| | |
line_width(index) | |
vertical_line(index) | |
horizontal_line(index) | |
end | |
end | |
def line_width(index) | |
@document.line_width = (index % BOX_SIZE).zero? ? THICK_LINE_WIDTH : THIN_LINE_WIDTH | |
end | |
def vertical_line(index) | |
start_x = grid_position(index) | |
@document.stroke { @document.line([start_x, grid_top], [start_x, grid_bottom]) } | |
end | |
def horizontal_line(index) | |
start_y = grid_top - index * @grid_cell_size | |
@document.stroke { @document.line([grid_left, start_y], [grid_right, start_y]) } | |
end | |
def fill_cell_values(values) | |
values.each_with_index do |row, index| | |
row.each_with_index do |cell, indey| | |
draw_cell_value(cell, index, indey) | |
end | |
end | |
end | |
def draw_cell_value(cell, index, indey) | |
return if cell.zero? | |
position = cell_position(index, indey) | |
@document.draw_text cell.to_s, at: position, size: 20, valign: :center | |
end | |
def cell_position(index, indey) | |
[ | |
grid_left + indey * @grid_cell_size + @grid_cell_size / 2, | |
grid_top - index * @grid_cell_size - @grid_cell_size / 2 | |
] | |
end | |
def last_difficulty?(index, difficulties) | |
index == difficulties.size - 1 | |
end | |
def grid_left | |
@document.bounds.left + @document.bounds.width / 2 - @grid_size / 2 | |
end | |
def grid_top | |
@document.bounds.top - @document.bounds.height / 2 + @grid_size / 2 | |
end | |
def grid_right | |
grid_left + GRID_SIZE * @grid_cell_size | |
end | |
def grid_bottom | |
grid_top - GRID_SIZE * @grid_cell_size | |
end | |
def grid_position(index) | |
grid_left + index * @grid_cell_size | |
end | |
end | |
class SudokuGenerator | |
def initialize | |
@grid = Array.new(9) { Array.new(9, 0) } | |
end | |
def solve(cell = 0) | |
return true if cell == 81 | |
row, col = cell.divmod(9) | |
return solve(cell + 1) if @grid[row][col] != 0 | |
(1..9).to_a.shuffle.each do |num| | |
next unless valid?(row, col, num) | |
@grid[row][col] = num | |
return true if solve(cell + 1) | |
@grid[row][col] = 0 | |
end | |
false | |
end | |
def valid?(row, col, num) | |
return false if row_contains?(row, num) || col_contains?(col, num) || box_contains?(row, col, num) | |
true | |
end | |
def row_contains?(row, num) | |
@grid[row].include?(num) | |
end | |
def col_contains?(col, num) | |
@grid.any? { |row| row[col] == num } | |
end | |
def box_contains?(row, col, num) | |
box_start_row = row - row % 3 | |
box_start_col = col - col % 3 | |
3.times do |i| | |
3.times do |j| | |
return true if @grid[box_start_row + i][box_start_col + j] == num | |
end | |
end | |
false | |
end | |
def remove_numbers(difficulty = :medium) | |
remove_count = removal_count(difficulty) | |
remove_count.times do | |
row = rand(9) | |
col = rand(9) | |
@grid[row][col] = 0 | |
end | |
end | |
def removal_count(difficulty) | |
case difficulty | |
when :easy then 35 | |
when :medium then 50 | |
when :hard then 75 | |
else raise ArgumentError, "Invalid difficulty level" | |
end | |
end | |
def display | |
@grid | |
end | |
end | |
sudoku = SudokuPDF.new | |
sudoku.generate_multi_page_pdf |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment