Skip to content

Instantly share code, notes, and snippets.

@kobaltz
Created September 15, 2023 17:42
Show Gist options
  • Save kobaltz/8e811ce7965baf9b5e1685a1990cb9a5 to your computer and use it in GitHub Desktop.
Save kobaltz/8e811ce7965baf9b5e1685a1990cb9a5 to your computer and use it in GitHub Desktop.
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