Skip to content

Instantly share code, notes, and snippets.

@nibzard
Created August 5, 2024 18:33
Show Gist options
  • Select an option

  • Save nibzard/2c5ffcd1784f795042055af72815224f to your computer and use it in GitHub Desktop.

Select an option

Save nibzard/2c5ffcd1784f795042055af72815224f to your computer and use it in GitHub Desktop.
!pip install reportlab
!pip install pypdf2
import random
import math
from numba import njit, cuda
import numpy as np
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.lib.units import cm
import io
def get_box_size(size):
if size in [4, 9, 16]:
root = int(size ** 0.5)
return root, root
elif size == 6:
return 2, 3
elif size == 12:
return 3, 4
else:
raise ValueError("Unsupported size. Supported sizes: 4, 6, 9, 12, 16.")
@cuda.jit
def fill_sudoku_kernel(grid, size, permutations, rows_per_box, cols_per_box):
row, col = cuda.grid(2)
if row < size and col < size:
num = permutations[(row * cols_per_box + row // rows_per_box + col) % size]
grid[row, col] = num
def fill_sudoku(grid, size):
permutations = np.random.permutation(size) + 1 # Random permutation of numbers 1 to size
d_permutations = cuda.to_device(permutations)
rows_per_box, cols_per_box = get_box_size(size)
# For smaller grids like 6x6, use a smaller threads_per_block
if size <= 9:
threads_per_block = (8, 8) # Smaller block size for smaller grids
else:
threads_per_block = (16, 16) # Larger block size for larger grids
# Calculate the number of blocks in the grid
blocks_per_grid_x = max(1, math.ceil(size / threads_per_block[0]))
blocks_per_grid_y = max(1, math.ceil(size / threads_per_block[1]))
blocks_per_grid = (blocks_per_grid_x, blocks_per_grid_y)
# Launch the kernel with the calculated grid size
fill_sudoku_kernel[blocks_per_grid, threads_per_block](grid, size, d_permutations, rows_per_box, cols_per_box)
cuda.synchronize()
@njit
def is_valid_move_cpu(grid, row, col, num, size, rows_per_box, cols_per_box):
for x in range(size):
if grid[row][x] == num or grid[x][col] == num:
return False
start_row, start_col = (row // rows_per_box) * rows_per_box, (col // cols_per_box) * cols_per_box
for i in range(rows_per_box):
for j in range(cols_per_box):
if grid[start_row + i][start_col + j] == num:
return False
return True
@njit
def solve_sudoku_cpu(grid, size, rows_per_box, cols_per_box):
for row in range(size):
for col in range(size):
if grid[row][col] == 0:
for num in range(1, size + 1):
if is_valid_move_cpu(grid, row, col, num, size, rows_per_box, cols_per_box):
grid[row][col] = num
if solve_sudoku_cpu(grid, size, rows_per_box, cols_per_box):
return True
grid[row][col] = 0
return False
return True
def generate_sudoku_cpu(size, difficulty):
supported_sizes = [4, 6, 9, 12, 16]
if size not in supported_sizes:
raise ValueError(f"Unsupported size. Supported sizes: {supported_sizes}.")
grid = np.zeros((size, size), dtype=np.int32)
d_grid = cuda.to_device(grid)
fill_sudoku(d_grid, size) # Fill grid with a randomized sequence
grid = d_grid.copy_to_host()
rows_per_box, cols_per_box = get_box_size(size)
if not solve_sudoku_cpu(grid, size, rows_per_box, cols_per_box):
raise ValueError("Failed to create a complete Sudoku grid.")
original_grid = grid.copy()
cells_to_remove = int(size * size * difficulty)
while cells_to_remove > 0:
row, col = random.randint(0, size-1), random.randint(0, size-1)
if grid[row][col] != 0:
grid[row][col] = 0
cells_to_remove -= 1
return grid, original_grid # Return both puzzle and solution
def create_sudoku_pdf(puzzle, solution, filename="sudoku_puzzle_with_solution.pdf"):
size = len(puzzle)
rows_per_box, cols_per_box = get_box_size(size)
# Set the page size to A4
page_width, page_height = A4
# Increase the margins for more comfortable viewing
margin = 2 * cm
# Adjust the maximum grid size to take the increased margins into account
max_grid_size = min(page_width - 2 * margin, page_height - 2 * margin)
# Calculate a smaller cell size so that the grid fits well within the margins
cell_size = max_grid_size / size
buffer = io.BytesIO()
c = canvas.Canvas(buffer, pagesize=A4)
c.setLineWidth(0.5)
def draw_grid(x_offset, y_offset, grid, cell_size, font_size):
grid_size = size * cell_size
c.setLineWidth(2)
c.rect(x_offset, y_offset, grid_size, grid_size)
c.setLineWidth(0.5)
for i in range(1, size):
x = x_offset + i * cell_size
y = y_offset + i * cell_size
if i % cols_per_box == 0:
c.setLineWidth(2)
c.line(x, y_offset, x, y_offset + grid_size)
c.setLineWidth(0.5)
if i % rows_per_box == 0:
c.setLineWidth(2)
c.line(x_offset, y, x_offset + grid_size, y)
c.setLineWidth(0.5)
for row in range(size):
for col in range(size):
if grid[row][col] != 0:
x = x_offset + (col + 0.5) * cell_size
y = y_offset + (size - row - 0.5) * cell_size
c.setFont("Helvetica", font_size)
c.drawCentredString(x, y - 4, str(grid[row][col]))
# Adjust y_offset based on margin and cell size
y_offset = page_height - margin - size * cell_size
draw_grid(margin, y_offset, puzzle, cell_size, font_size=cell_size*0.4)
solution_cell_size = cell_size / 2.5
solution_grid_size = size * solution_cell_size
solution_y_offset = y_offset - margin - solution_grid_size
draw_grid(margin, solution_y_offset, solution, solution_cell_size, font_size=solution_cell_size*0.4)
c.setFont("Helvetica-Bold", 14)
c.drawString(margin, y_offset + size * cell_size + 10, "Sudoku Puzzle")
c.drawString(margin, solution_y_offset + solution_grid_size + 10, "Solution")
c.save()
buffer.seek(0)
return buffer
# Example usage
size = 16
difficulty = 0.7
puzzle, solution = generate_sudoku_cpu(size, difficulty)
pdf_buffer = create_sudoku_pdf(puzzle, solution)
with open("sudoku_puzzle_with_solution.pdf", "wb") as f:
f.write(pdf_buffer.getvalue())
print("Sudoku puzzle with solution PDF has been saved to 'sudoku_puzzle_with_solution.pdf'")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment