前文提到了 5宫格(25格子,1-12空格散开,12和13是棋盘样式,13以后不明显)
1.(越往后实际可以排序的答案样式也越多。预设答案只是其中一种)
2.超过一半的关卡时,空格就不是分散的了(超过一半数量),不容易做。
所以我不想做全部关卡,就做一半。
一、棋盘测试




可以看到,这是分散样式,类似棋盘,这是空格最多的棋盘样式。
用Python做棋盘,了解1-10宫格中每种最多的棋盘样式
python
'''
python制作数独宫格的棋盘样式宫格1-宫格10
deepseek,阿夏
20250803
'''
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec
def create_chessboard(n, top_left_white=True):
"""创建n x n的棋盘,返回矩阵和白色格子数量"""
board = np.zeros((n, n))
white_count = 0
for i in range(n):
for j in range(n):
if (i + j) % 2 == (0 if top_left_white else 1):
board[i, j] = 1 # 白色
white_count += 1
else:
board[i, j] = 0 # 黑色
return board, white_count
# 创建一个大图来显示所有棋盘
plt.figure(figsize=(15, 12))
plt.suptitle("Chessboards from 1x1 to 10x10 with Maximum White Squares", fontsize=16)
# 使用GridSpec来布局子图
gs = GridSpec(4, 5, width_ratios=[1]*5, height_ratios=[1]*4)
max_white_counts = []
for size in range(1, 11):
# 计算两种排列方式的白格子数量
board1, white_count1 = create_chessboard(size, True)
board2, white_count2 = create_chessboard(size, False)
# 选择白格子数量较多的排列方式
if white_count1 >= white_count2:
board = board1
white_count = white_count1
top_left_color = "White"
else:
board = board2
white_count = white_count2
top_left_color = "Black"
max_white_counts.append((size, white_count))
# 添加子图
ax = plt.subplot(gs[(size-1)//3, (size-1)%3 + ((size-1)//3)*2])
ax.imshow(board, cmap='binary', vmin=0, vmax=1)
ax.set_title(f"{size}x{size}\nWhite: {white_count}", pad=10)
ax.set_xticks([])
ax.set_yticks([])
# 在左上角标记颜色
ax.text(0, 0, top_left_color[0], ha='center', va='center',
color='red' if top_left_color == "White" else 'blue',
fontsize=10, fontweight='bold')
plt.tight_layout()
plt.show()
# 打印最大白格子数量表
print("\nMaximum white squares for each board size:")
print("-" * 40)
print("| Size | White Squares (max) | Formula |")
print("-" * 40)
for size, count in max_white_counts:
if size % 2 == 0:
formula = f"n²/2 = {size*size//2}"
else:
formula = f"(n²+1)/2 = {(size*size+1)//2}"
print(f"| {size}x{size:<3} | {count:<19} | {formula:<13} |")
print("-" * 40)


结果显示:
奇数宫格的最大数量是:"(奇数*奇数+1)/2
偶数宫格的最大数量是:"(偶数*偶数)/2
修改代码
把原来的动物数独代码与奇偶规律合并。

测试了一上午,deepseek写了20多条,终于可以用了
python
# -*- coding:utf-8 -*-
'''
制作动物/脸谱数独N宫格通用版(3-10的黏贴关卡 A4 1图2图6图的所有组合 还有一个限定A4大小的0图(3-6宫格)
1.无答案:9种3:3,两两组合
2.有答案:27种 3:3:3,三三组合(实际需要其中9种)
3.横竖两个不能都是空格,空格尽量分散排列,如9格,目前1-5可以分散,空额不相连,6-9不行)
4.奇数宫格:(奇数*奇数+1)/2;偶数宫格:(偶数*偶数+1)/2
作者:AI对话大师,deepseek、阿夏
时间:2025年08月03日
'''
import os
import time
from docx import Document
from docx.shared import Cm, Pt, RGBColor
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
from docxtpl import DocxTemplate
import pandas as pd
from docx2pdf import convert
from win32com.client import constants, gencache
from win32com.client.gencache import EnsureDispatch
from datetime import datetime
import random
import shutil
import xlwt
import xlrd
from PyPDF2 import PdfMerger
from docx.shared import Pt, Cm, RGBColor
from docx.oxml.shared import qn
num=1
hsall=int(input('几宫格?(3-10)\n'))
start=0
end=hsall
start_time = datetime.now()
hs=hsall
path=fr'C:\Users\jg2yXRZ\OneDrive\桌面\20250803动物数独N宫格原始6图'
A4size=[6.5,4.88,3.9,3.25,2.79,2.44,2.17]
A4gz=[3,4,5,6,7,8,9]
A4pg=[3,4,5,6,7,8,9]
A4gzs=[3,4,5,6,7,8,9]
psize=[19.6,14.1,9.39]
gzsize=[6.4,4.8,3.84,3.2,2.74,2.39,2.12,1.91]
gzsgg=list(range(3, 10))
pg=[1,2,6]
gzs=[1,2,2]
print('-----第一板块、动物操作卡(大图片卡一页1图、1页2图 一页6图)-------')
# Get page settings based on hs value
for w in range(len(A4gz)):
if hs == A4gz[w]:
psize.append(A4size[w])
pg = [1, 2, 6, A4pg[w]]
gzs = [1, 2, 2, A4gzs[w]]
k = ['1图关卡', '2图关卡', '6图关卡', f'A4的{hs}图卡片']
print(k)
for pt in range(len(pg)):
# Create temp folders
temp_word_path = os.path.join(path, '零时Word')
temp_pdf_path = os.path.join(path, '05操作卡pdf')
os.makedirs(temp_word_path, exist_ok=True)
os.makedirs(temp_pdf_path, exist_ok=True)
# Load template document
doc = Document(os.path.join(path, f'动物数独({k[pt]}).docx'))
table = doc.tables[0]
# Get animal images
pic_folder = os.path.join(path, '02动物图片')
pic_list = [os.path.join(pic_folder, f) for f in os.listdir(pic_folder)][start:end] * hs
# Group images based on layout
group_size = pg[pt]*pg[pt] if pt == 3 else pg[pt]
groups = [pic_list[i:i + group_size] for i in range(0, len(pic_list), group_size)]
for group_idx, group in enumerate(groups):
doc = Document(os.path.join(path, f'动物数独({k[pt]}).docx'))
table = doc.tables[0]
for cell_idx, img_file in enumerate(group):
row = int(cell_idx / gzs[pt])
col = cell_idx % gzs[pt]
cell = table.cell(row, col)
cell.paragraphs[0].clear()
if os.path.exists(img_file):
run = cell.paragraphs[0].add_run()
run.add_picture(img_file, width=Cm(psize[pt]), height=Cm(psize[pt]))
cell.paragraphs[0].alignment = 1
# Add title for A4 layout
if pt == 3:
title_table = doc.tables[1]
title_cell = title_table.cell(0, 0)
title_cell.text = f'动物数独 {hs}*{hs} 操作卡'
para = title_cell.paragraphs[0]
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = para.runs[0]
run.font.name = '黑体'
run.font.size = Pt(35)
run.font.color.rgb = RGBColor(0, 0, 0)
run._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
# Save and convert to PDF
doc.save(os.path.join(temp_word_path, f'{group_idx+1:02d}页.docx'))
convert(os.path.join(temp_word_path, f'{group_idx+1:02d}页.docx'),
os.path.join(temp_word_path, f'{group_idx+1:02d}页.pdf'))
time.sleep(2.5)
# Merge PDFs
pdf_files = sorted([os.path.join(temp_word_path, f) for f in os.listdir(temp_word_path) if f.endswith('.pdf')])
merger = PdfMerger()
for pdf in pdf_files:
merger.append(pdf)
output_name = "03 0图关卡图操作卡片.pdf" if pt == 3 else f"03 {k[pt]}图操作卡片.pdf"
merger.write(os.path.join(temp_pdf_path, output_name))
merger.close()
# Clean up
shutil.rmtree(temp_word_path)
print('-----第二板块、动物任务卡和答案卡一套,只要JPG-------')
import random
from win32com.client import constants, gencache
from win32com.client import genpy
from win32com.client.gencache import EnsureDispatch
from docx import Document
from docx.shared import Pt, RGBColor
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.oxml.ns import qn
from docx.enum.text import WD_ALIGN_PARAGRAPH
import docxtpl
import pandas as pd
from docx2pdf import convert
from docx.oxml.ns import nsdecls
from docx.oxml import parse_xml
import copy
print('------3-1、生成指定数量的关卡(按最大空缺数限制)------')
n = hsall # 宫格数(例如3)
g = n * n # 总格子数(例如9)
# 根据宫格数确定最大空缺数
if n % 2 == 1: # 奇数宫格
max_empty = (g + 1) // 2 # 例如3宫格:(9+1)/2=5
else: # 偶数宫格
max_empty = g // 2 # 例如4宫格:16/2=8
# 只生成1到max_empty关(例如3宫格只生成5关)
a = []
for empty in range(1, max_empty + 1): # 直接按空缺数生成
# 计算对应的百分比(四舍五入)
percentage = round(empty * 100 / g)
print(f'{n}宫格,难度{percentage:03d},抽取{percentage:03d}%:实际有{empty:03d}空,已有图案{g-empty:03d}')
a.append(f'{n}宫格,难度{percentage:03d},抽取{percentage:03d}%:实际有{empty:03d}空,已有图案{g-empty:03d}')
# 去重和排序(虽然现在应该已经是有序且不重复的)
b = []
for element in a:
parts = element.split(":")
info = parts[1]
b.append(info)
b = list(set(b))
b.sort(reverse=False)
f = []
for d in range(len(b)):
for c in a:
if c[-15:] == b[d]:
f.append(c)
break
# 移除第一个(空0的无效项)如果有的话
if f and int(f[0][20:23]) == g: # 检查是否全满(空0)
f.pop(0)
print(f'实际生成关卡数:{len(f)}')
# 提取空缺数和已有图案数
empty_counts = []
filled_counts = []
for p in f:
empty_counts.append(int(p[20:23]))
filled_counts.append(int(p[12:15]))
print('空缺数序列:', empty_counts)
print('已有图案数:', filled_counts)
print('------2、制作宫格随机数字------')
def generate_non_adjacent_blanks(hs, num_blanks):
"""生成不连续的空格位置"""
max_blanks = (hs*hs + 1)//2 if hs % 2 == 1 else (hs*hs)//2
num_blanks = min(num_blanks, max_blanks) # 确保不超过最大空格数
positions = list(range(hs*hs))
blanks = []
# 尝试生成不连续的空格
max_attempts = 1000
attempts = 0
while len(blanks) < num_blanks and attempts < max_attempts:
temp_blanks = []
remaining_positions = positions.copy()
while len(temp_blanks) < num_blanks and remaining_positions:
pos = random.choice(remaining_positions)
row = pos // hs
col = pos % hs
# 检查是否与已有空格相邻
valid = True
for blank in temp_blanks:
blank_row = blank // hs
blank_col = blank % hs
# 检查是否在同一行或同一列且相邻
if (row == blank_row and abs(col - blank_col) == 1) or \
(col == blank_col and abs(row - blank_row) == 1):
valid = False
break
if valid:
temp_blanks.append(pos)
# 移除相邻位置
adjacent_positions = []
if col > 0: adjacent_positions.append(pos-1)
if col < hs-1: adjacent_positions.append(pos+1)
if row > 0: adjacent_positions.append(pos-hs)
if row < hs-1: adjacent_positions.append(pos+hs)
for adj_pos in adjacent_positions:
if adj_pos in remaining_positions:
remaining_positions.remove(adj_pos)
remaining_positions.remove(pos) if pos in remaining_positions else None
if len(temp_blanks) == num_blanks:
blanks = temp_blanks
break
attempts += 1
# 如果无法生成不连续的空格,则返回随机空格
if len(blanks) < num_blanks:
blanks = random.sample(positions, num_blanks)
return blanks
# 修改这里 - 使用empty_counts而不是g
for kk in range(len(empty_counts)): # 遍历每个空缺数
ll=['{}'.format(hs)]
mm=['11']
nn=['36']
for r in range(len(ll)):
if hs ==int(ll[r]):
db=int(mm[r][0])
cb=int(mm[r][1])
size=int(nn[r])
imagePath=path+r'\\零时Word'
os.makedirs(imagePath,exist_ok=True)
imagePatha=path+r'\06答案卡'
imagePathq=path+r'\07任务卡'
os.makedirs(imagePatha,exist_ok=True)
os.makedirs(imagePathq,exist_ok=True)
db_size = hs*db
cb_size= hs*cb
print('{}宫格排列底{}侧{}共{}套,底边格子数{}'.format(hs,db,cb,db*cb,db_size))
print('{}宫格排列底{}侧{}共{}套,侧边格子数{}'.format(hs,db,cb,db*cb,cb_size))
bgszm=[]
for a in range(0,cb_size,hs):
for b in range(0,db_size,hs):
bgszm.append('{}{}'.format('%02d'%a,'%02d'%b))
print(bgszm)
start_coordinates = [(int(s[0:2]), int(s[2:4])) for s in bgszm]
cell_coordinates = []
for start_coord in start_coordinates:
i, j = start_coord
subgrid_coordinates = []
for x in range(hs):
for y in range(hs):
subgrid_coordinates.append((i + x, j + y))
cell_coordinates.append(subgrid_coordinates)
bg=[]
for coordinates in cell_coordinates:
for c in coordinates:
print(c)
s = ''.join(str(num).zfill(2) for num in c)
print(str(s))
bg.append(s)
print(bg)
P=[]
for z in range(num):
P.clear()
for j in range(db*cb):
def generate_sudoku_board():
board = [[0] * hs for _ in range(hs)]
def filling_board(row, col):
if row == hs:
return True
next_row = row if col < hs-1 else row + 1
next_col = (col + 1) % hs
import math
r = int(math.sqrt(hs))
print(r)
box_row = row // r
box_col = col // r
numbers = random.sample(range(1, hs+1), hs)
for num in numbers:
if hs==4 or hs==9:
if num not in board[row] and all(board[i][col] != num for i in range(hs)) and all(num != board[i][j] for i in range(box_row*r, box_row*r+r) for j in range(box_col*r, box_col*r+r)):
board[row][col] = num
if filling_board(next_row, next_col):
return True
board[row][col] = 0
else:
if num not in board[row] and all(board[i][col] != num for i in range(hs)) and all(num != board[i][j] for i in range(box_row, box_row) for j in range(box_col, box_col)):
board[row][col] = num
if filling_board(next_row, next_col):
return True
board[row][col] = 0
return False
filling_board(0, 0)
return board
board1 = generate_sudoku_board()
def create_board1():
return board1
def create_board2():
global board1
board2 = copy.deepcopy(board1)
# 使用empty_counts[kk]获取当前关卡的空缺数
num_blanks = empty_counts[kk]
# 使用新方法生成不连续的空格
blanks = generate_non_adjacent_blanks(hs, num_blanks)
for i in blanks:
row = i // hs
col = i % hs
board2[row][col] = 0
return board2
v1 = create_board1()
v2 = create_board2()
v=v1+v2
print(v)
P = ['' if a2 == 0 else a2 for a1 in v for a2 in a1]
print(P)
print(len(P))
half_len = int(len(P) // 2)
Q = [P[i:i+half_len] for i in range(0, len(P), half_len)]
print(Q)
time.sleep(2.5)
print('------答案卡和题目卡------')
title=['答案卡','任务卡']
imagePath1=path+r'\答案卡1'
imagePath2=path+r'\任务卡1'
os.makedirs(imagePath1,exist_ok=True)
os.makedirs(imagePath2,exist_ok=True)
ti=[path+r'\答案卡1',path+r'\任务卡1']
for tt in range(len(Q)):
doc = Document(path+fr'\动物数独(横板{hs}宫格).docx')
table = doc.tables[0]
for b1 in range(0,1):
cell_00 = table.cell(0, b1)
cell_00_paragraph = cell_00.paragraphs[0]
cell_00_paragraph.text =f"动物数独 {hs}*{hs} 第 {kk+1} 关 {title[tt]}"
cell_00_paragraph.style.font.bold = True
cell_00_paragraph.style.font.size = Pt(24)
cell_00_paragraph.style.font.name = "黑体"
cell_00_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
table = doc.tables[1]
for t in range(0,len(bg)):
pp=int(bg[t][0:2])
qq=int(bg[t][2:4])
k=str(Q[tt][t])
print(pp,qq,k)
run=table.cell(pp,qq).paragraphs[0].add_run(k)
run.font.name = '黑体'
run.font.size = Pt(size)
run.font.color.rgb = RGBColor(0,0,0)
run.bold=True
r = run._element
r.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
table.cell(pp,qq).paragraphs[0].alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
doc.save(ti[tt]+fr'\{z+1:02d}.docx')
time.sleep(2.5)
print('------4、每张word转为pdf----')
from docx import Document
from docx.shared import Cm
animal_path = path+r'\02动物图片'
file_paths = [os.path.join(animal_path, file_name) for file_name in os.listdir(animal_path)]
print(file_paths)
file_paths=file_paths[start:end]
doc = Document(ti[tt]+fr'\{z+1:02d}.docx')
tables = doc.tables
for table in tables:
for i, row in enumerate(table.rows):
for j, cell in enumerate(row.cells):
cell_text = cell.text
for x in range(0,hs):
if cell_text == f'{x+1}':
cell.text = ''
run = cell.paragraphs[0].add_run()
for n in range(len(gzsgg)):
if hs==gzsgg[n]:
run.add_picture(file_paths[x], width=Cm(gzsize[n]), height=Cm(gzsize[n]))
run.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
cell.vertical_alignment = WD_ALIGN_PARAGRAPH.CENTER
cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
doc.save(ti[tt]+fr'\{z+1:02d}.docx')
time.sleep(2.5)
from docx2pdf import convert
inputFile = ti[tt]+fr'\{z+1:02d}.docx'
outputFile =ti[tt]+fr'\{z+1:02d}.pdf'
f1 = open(outputFile, 'w')
f1.close()
convert(inputFile, outputFile)
time.sleep(2.5)
print('----------更改pdf新名字------------')
tii=[path+r'\06答案卡',path+r'\07任务卡']
for t3 in range(len(tii)):
pdf_lst = [f for f in os.listdir(ti[t3]) if f.endswith('.pdf')]
pdf_path = os.path.join(ti[t3], pdf_lst[0])
print(pdf_path)
# 修改这里 - 使用empty_counts[kk]而不是w[kk]
new_file_name = f"{title[t3]} 动物拼图{hs}宫格 难度{kk+1:02d} 空{empty_counts[kk]:03d}格({db}乘{cb}等于{num}份{db*cb}张).pdf"
output_path = os.path.join(tii[t3], new_file_name)
print(output_path)
os.rename(pdf_path, output_path)
time.sleep(2.5)
shutil.rmtree(ti[t3])
time.sleep(2.5)
print('----------第4步:把都有16管PDF关卡变png------------')
from win32com.client import Dispatch
import os
import re
import fitz
wdFormatPDF = 17
zoom_x=2
zoom_y=2
rotation_angle=0
li=[path+r'\06答案卡',path+r'\07任务卡']
for l in li:
for root, dirs, files in os.walk(l):
for file in files:
if re.search('\.pdf$', file):
filename = os.path.abspath(root + "\\" + file)
print(filename)
pdf = fitz.open(filename)
for pg in range(0, pdf.pageCount):
page = pdf[pg]
trans = fitz.Matrix(zoom_x, zoom_y).preRotate(rotation_angle)
pm = page.getPixmap(matrix=trans, alpha=True)
pm.writePNG(filename.replace('.pdf', '') + str(pg+1) + ".png")
pdf.close()
for parent, dirnames, filenames in os.walk(l):
for fn in filenames:
if fn.lower().endswith('.pdf'):
os.remove(os.path.join(parent, fn))
print('-----第三板块、把动物任务卡和答案卡一套分别做成1页1份,1页2份,1页6份的pdf-------')
psize=[9.39,9.39,14.1,14.1,19.6,19.6]
pg=[6,6,2,2,1,1]
gzs=[2,2,2,2,1,1]
nh=[1,2,1,2,1,2]
for x in range(len(pg)):
imagePath11=path+r'\08答案卡pdf'
imagePath22=path+r'\09任务卡pdf'
os.makedirs(imagePath11,exist_ok=True)
os.makedirs(imagePath22,exist_ok=True)
tiii=[path+r'\08答案卡pdf',path+r'\09任务卡pdf',path+r'\08答案卡pdf',path+r'\09任务卡pdf',path+r'\08答案卡pdf',path+r'\09任务卡pdf']
tii=[path+r'\06答案卡',path+r'\07任务卡',path+r'\06答案卡',path+r'\07任务卡',path+r'\06答案卡',path+r'\07任务卡']
title=['答案卡','任务卡','答案卡','任务卡','答案卡','任务卡']
imagePath=path+r'\\零时Word'
os.makedirs(imagePath,exist_ok=True)
doc = Document(path +fr'\动物数独({pg[x]}图关卡).docx')
tables = doc.tables[0]
file_paths = [os.path.join(tii[x], file) for file in os.listdir(tii[x])]
pic_list_six = [file_paths[i:i+int(pg[x])] for i in range(0, len(file_paths), int(pg[x]))]
print(pic_list_six)
print(len(pic_list_six))
for group_index, group in enumerate(pic_list_six):
doc = Document(path + fr'\动物数独({pg[x]}图关卡).docx')
for cell_index, image_file in enumerate(group):
cc=float(psize[x])
table = doc.tables[0]
cell = table.cell(int(cell_index /gzs[x]), cell_index % gzs[x])
cell_paragraph = cell.paragraphs[0]
cell_paragraph.clear()
run = cell_paragraph.add_run()
run.add_picture(os.path.join(image_file), width=Cm(cc), height=Cm(cc))
cell_paragraph.alignment = 1
doc.save(imagePath + fr'\{group_index + 1:02d}页.docx')
inputFile = imagePath + fr'\{group_index + 1:02d}页.docx'
outputFile = imagePath + fr'\{group_index + 1:02d}页.pdf'
convert(inputFile, outputFile)
time.sleep(2.5)
pdf_lst = [f for f in os.listdir(imagePath) if f.endswith('.pdf')]
pdf_lst = [os.path.join(imagePath, filename) for filename in pdf_lst]
pdf_lst.sort()
file_merger = PdfMerger()
for pdf in pdf_lst:
print(pdf)
file_merger.append(pdf)
file_merger.write(tiii[x]+ fr"\{nh[x]:02} {pg[x]:02}图{title[x]}.pdf")
time.sleep(1)
file_merger.close()
shutil.rmtree(imagePath)
print('-----第四板块、交叉合并有答案6套,无答案6套(1-1-1、1-1,1-2-2,1-2,2-1-1,2-1......----')
newgz=path+fr'\(打印)动物数独{hs}宫格24套'
os.makedirs(newgz,exist_ok=True)
address=[]
all=['09任务卡pdf','05操作卡pdf']
for y in all:
new=path+fr'\{y}'
file_new = [os.path.join(new, f) for f in os.listdir(new)]
address.append(file_new)
combinations = [[x, y] for x in address[0] for y in address[1]]
print(combinations)
import PyPDF2
for files in combinations:
print(files)
pdf_merger = PyPDF2.PdfFileMerger()
for file in files:
pdf_merger.append(file)
output_file = newgz+fr"\动物数独{hs}宫格(无答案) {files[0][-9:-4] } {files[1][-13:-4] }.pdf"
pdf_merger.write(output_file)
pdf_merger.close()
address=[]
all=['08答案卡pdf','09任务卡pdf','05操作卡pdf']
for y in all:
new=path+fr'\{y}'
file_new = [os.path.join(new, f) for f in os.listdir(new)]
address.append(file_new)
combinations = []
for i in address[0]:
for j in address[1]:
for k in address[2]:
combinations.append([i, j, k])
import PyPDF2
for files in combinations:
print(files)
pdf_merger = PyPDF2.PdfFileMerger()
for file in files:
pdf_merger.append(file)
output_file = newgz+fr"\动物数独{hs}宫格(有答案) {files[0][-9:-4] } {files[1][-9:-4]} {files[2][-13:-4]}.pdf"
pdf_merger.write(output_file)
pdf_merger.close()
import os
import shutil
for file_name in os.listdir(newgz):
if file_name.endswith(".pdf"):
if f"动物数独{hs}宫格(有答案)" in file_name:
if hs<10:
if file_name[13] == file_name[19]:
print(f"保留文件:{file_name}")
else:
os.remove(os.path.join(newgz, file_name))
print(f"删除文件:{file_name}")
imagePath11=path+r'\08答案卡pdf'
imagePath22=path+r'\09任务卡pdf'
imagePatha=path+r'\06答案卡'
imagePathq=path+r'\07任务卡'
imagePathpaste=path+r'\05操作卡pdf'
shutil.rmtree(imagePath11)
shutil.rmtree(imagePath22)
shutil.rmtree(imagePatha)
shutil.rmtree(imagePathq)
shutil.rmtree(imagePathpaste)
end_time = datetime.now()
elapsed_time = end_time - start_time
print(f"动物数独{hs}宫格程序开始时间:{start_time}")
print(f"动物数独{hs}宫格程序结束时间:{end_time}")
print("程序运行时间:", elapsed_time)
这个代码只能生成一个,如3宫格、4宫格。我希望一次性生成3-6宫格。(由于操作卡的0图有标题,所以操作部分不能通用)
python
# -*- coding:utf-8 -*-
'''
制作动物/脸谱数独N宫格通用版(3-10的黏贴关卡 A4 1图2图6图的所有组合 还有一个限定A4大小的0图(3-6宫格)
1.无答案:9种3:3,两两组合
2.有答案:27种 3:3:3,三三组合(实际需要其中9种)
3.横竖两个不能都是空格,空格尽量分散排列,如9格,目前1-5可以分散,空额不相连,6-9不行)
4.奇数宫格:(奇数*奇数+1)/2;偶数宫格:(偶数*偶数+1)/2
5.一次性生成3-6宫格
作者:AI对话大师,deepseek、阿夏
时间:2025年08月03日
'''
import os
import time
from docx import Document
from docx.shared import Cm, Pt, RGBColor
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
from docxtpl import DocxTemplate
import pandas as pd
from docx2pdf import convert
from win32com.client import constants, gencache
from win32com.client.gencache import EnsureDispatch
from datetime import datetime
import random
import shutil
import xlwt
import xlrd
from PyPDF2 import PdfMerger
from docx.shared import Pt, Cm, RGBColor
from docx.oxml.shared import qn
import math
import fitz
from PyPDF2 import PdfFileMerger
import copy
start_time = datetime.now()
# 改为循环处理多个宫格数
hsall_list = [3, 4, 5, 6] # 要生成的宫格数列表
for hsall in hsall_list:
start = 0
end = hsall=hs
# 为每个宫格数创建独立的文件夹
path = fr'C:\Users\jg2yXRZ\OneDrive\桌面\20250803动物数独N宫格原始6图'
os.makedirs(path, exist_ok=True)
# A4尺寸设置
A4size = [6.5, 4.88, 3.9, 3.25, 2.79, 2.44, 2.17]
A4gz = [3, 4, 5, 6, 7, 8, 9]
A4pg = [3, 4, 5, 6, 7, 8, 9]
A4gzs = [3, 4, 5, 6, 7, 8, 9]
psize = [19.6, 14.1, 9.39]
gzsize = [6.4, 4.8, 3.84, 3.2, 2.74, 2.39, 2.12, 1.91]
gzsgg = list(range(3, 10))
pg = [1, 2, 6]
gzs = [1, 2, 2]
print('-----第一板块、动物操作卡(大图片卡一页1图、1页2图 一页6图)-------')
# 根据宫格数获取页面设置
for w in range(len(A4gz)):
if hs == A4gz[w]:
psize.append(A4size[w])
pg = [1, 2, 6, A4pg[w]]
gzs = [1, 2, 2, A4gzs[w]]
k = ['1图关卡', '2图关卡', '6图关卡', f'A4的{hs}图卡片']
print(k)
for pt in range(len(pg)):
# 创建临时文件夹
temp_word_path = os.path.join(path, '零时Word')
temp_pdf_path = os.path.join(path, '05操作卡pdf')
os.makedirs(temp_word_path, exist_ok=True)
os.makedirs(temp_pdf_path, exist_ok=True)
# 加载模板文档
doc = Document(os.path.join(path, f'动物数独({k[pt]}).docx'))
table = doc.tables[0]
# 获取动物图片
pic_folder = os.path.join(path, '02动物图片')
pic_list = [os.path.join(pic_folder, f) for f in os.listdir(pic_folder)][start:end] * hs
# 根据布局分组图片
group_size = pg[pt]*pg[pt] if pt == 3 else pg[pt]
groups = [pic_list[i:i + group_size] for i in range(0, len(pic_list), group_size)]
for group_idx, group in enumerate(groups):
doc = Document(os.path.join(path, f'动物数独({k[pt]}).docx'))
table = doc.tables[0]
for cell_idx, img_file in enumerate(group):
row = int(cell_idx / gzs[pt])
col = cell_idx % gzs[pt]
cell = table.cell(row, col)
cell.paragraphs[0].clear()
if os.path.exists(img_file):
run = cell.paragraphs[0].add_run()
run.add_picture(img_file, width=Cm(psize[pt]), height=Cm(psize[pt]))
cell.paragraphs[0].alignment = 1
# 为A4布局添加标题
if pt == 3:
title_table = doc.tables[1]
title_cell = title_table.cell(0, 0)
title_cell.text = f'动物数独 {hs}*{hs} 操作卡'
para = title_cell.paragraphs[0]
para.alignment = WD_ALIGN_PARAGRAPH.CENTER
run = para.runs[0]
run.font.name = '黑体'
run.font.size = Pt(35)
run.font.color.rgb = RGBColor(0, 0, 0)
run._element.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
# 保存并转换为PDF
doc.save(os.path.join(temp_word_path, f'{group_idx+1:02d}页.docx'))
convert(os.path.join(temp_word_path, f'{group_idx+1:02d}页.docx'),
os.path.join(temp_word_path, f'{group_idx+1:02d}页.pdf'))
time.sleep(3)
# 合并PDF
pdf_files = sorted([os.path.join(temp_word_path, f) for f in os.listdir(temp_word_path) if f.endswith('.pdf')])
merger = PdfMerger()
for pdf in pdf_files:
merger.append(pdf)
output_name = "03 0图关卡图操作卡片.pdf" if pt == 3 else f"03 {k[pt]}图操作卡片.pdf"
merger.write(os.path.join(temp_pdf_path, output_name))
merger.close()
# 清理临时文件
shutil.rmtree(temp_word_path)
print('-----第二板块、动物任务卡和答案卡一套,只要JPG-------')
print('------3-1、生成指定数量的关卡(按最大空缺数限制)------')
n = hsall # 宫格数
g = n * n # 总格子数
# 根据宫格数确定最大空缺数
if n % 2 == 1: # 奇数宫格
max_empty = (g + 1) // 2 # 例如3宫格:(9+1)/2=5
else: # 偶数宫格
max_empty = g // 2 # 例如4宫格:16/2=8
# 只生成1到max_empty关
a = []
for empty in range(1, max_empty + 1): # 直接按空缺数生成
percentage = round(empty * 100 / g)
print(f'{n}宫格,难度{percentage:03d},抽取{percentage:03d}%:实际有{empty:03d}空,已有图案{g-empty:03d}')
a.append(f'{n}宫格,难度{percentage:03d},抽取{percentage:03d}%:实际有{empty:03d}空,已有图案{g-empty:03d}')
# 去重和排序
b = []
for element in a:
parts = element.split(":")
info = parts[1]
b.append(info)
b = list(set(b))
b.sort(reverse=False)
f = []
for d in range(len(b)):
for c in a:
if c[-15:] == b[d]:
f.append(c)
break
# 移除第一个(空0的无效项)如果有的话
if f and int(f[0][20:23]) == g: # 检查是否全满(空0)
f.pop(0)
print(f'实际生成关卡数:{len(f)}')
# 提取空缺数和已有图案数
empty_counts = []
filled_counts = []
for p in f:
empty_counts.append(int(p[20:23]))
filled_counts.append(int(p[12:15]))
print('空缺数序列:', empty_counts)
print('已有图案数:', filled_counts)
print('------2、制作宫格随机数字------')
def generate_non_adjacent_blanks(hs, num_blanks):
"""生成不连续的空格位置"""
max_blanks = (hs*hs + 1)//2 if hs % 2 == 1 else (hs*hs)//2
num_blanks = min(num_blanks, max_blanks) # 确保不超过最大空格数
positions = list(range(hs*hs))
blanks = []
# 尝试生成不连续的空格
max_attempts = 1000
attempts = 0
while len(blanks) < num_blanks and attempts < max_attempts:
temp_blanks = []
remaining_positions = positions.copy()
while len(temp_blanks) < num_blanks and remaining_positions:
pos = random.choice(remaining_positions)
row = pos // hs
col = pos % hs
# 检查是否与已有空格相邻
valid = True
for blank in temp_blanks:
blank_row = blank // hs
blank_col = blank % hs
# 检查是否在同一行或同一列且相邻
if (row == blank_row and abs(col - blank_col) == 1) or \
(col == blank_col and abs(row - blank_row) == 1):
valid = False
break
if valid:
temp_blanks.append(pos)
# 移除相邻位置
adjacent_positions = []
if col > 0: adjacent_positions.append(pos-1)
if col < hs-1: adjacent_positions.append(pos+1)
if row > 0: adjacent_positions.append(pos-hs)
if row < hs-1: adjacent_positions.append(pos+hs)
for adj_pos in adjacent_positions:
if adj_pos in remaining_positions:
remaining_positions.remove(adj_pos)
remaining_positions.remove(pos) if pos in remaining_positions else None
if len(temp_blanks) == num_blanks:
blanks = temp_blanks
break
attempts += 1
# 如果无法生成不连续的空格,则返回随机空格
if len(blanks) < num_blanks:
blanks = random.sample(positions, num_blanks)
return blanks
def generate_valid_sudoku(hs):
"""生成有效的数独棋盘"""
board = [[0 for _ in range(hs)] for _ in range(hs)]
def is_valid(num, row, col):
# 检查行
if num in board[row]:
return False
# 检查列
for i in range(hs):
if board[i][col] == num:
return False
# 检查宫
box_size = int(math.sqrt(hs))
box_row = (row // box_size) * box_size
box_col = (col // box_size) * box_size
for i in range(box_size):
for j in range(box_size):
if board[box_row + i][box_col + j] == num:
return False
return True
def backtrack(row, col):
if row == hs:
return True
next_row = row if col < hs-1 else row + 1
next_col = (col + 1) % hs
if board[row][col] != 0:
return backtrack(next_row, next_col)
for num in random.sample(range(1, hs+1), hs):
if is_valid(num, row, col):
board[row][col] = num
if backtrack(next_row, next_col):
return True
board[row][col] = 0
return False
backtrack(0, 0)
return board
# 修改这里 - 使用empty_counts而不是g
for kk in range(len(empty_counts)): # 遍历每个空缺数
ll = ['{}'.format(hs)]
mm = ['11']
nn = ['36']
for r in range(len(ll)):
if hs == int(ll[r]):
db = int(mm[r][0])
cb = int(mm[r][1])
size = int(nn[r])
imagePath = path + r'\\零时Word'
os.makedirs(imagePath, exist_ok=True)
imagePatha = path + r'\06答案卡'
imagePathq = path + r'\07任务卡'
os.makedirs(imagePatha, exist_ok=True)
os.makedirs(imagePathq, exist_ok=True)
db_size = hs * db
cb_size = hs * cb
print('{}宫格排列底{}侧{}共{}套,底边格子数{}'.format(hs, db, cb, db*cb, db_size))
print('{}宫格排列底{}侧{}共{}套,侧边格子数{}'.format(hs, db, cb, db*cb, cb_size))
bgszm = []
for a in range(0, cb_size, hs):
for b in range(0, db_size, hs):
bgszm.append('{}{}'.format('%02d' % a, '%02d' % b))
print(bgszm)
start_coordinates = [(int(s[0:2]), int(s[2:4])) for s in bgszm]
cell_coordinates = []
for start_coord in start_coordinates:
i, j = start_coord
subgrid_coordinates = []
for x in range(hs):
for y in range(hs):
subgrid_coordinates.append((i + x, j + y))
cell_coordinates.append(subgrid_coordinates)
bg = []
for coordinates in cell_coordinates:
for c in coordinates:
print(c)
s = ''.join(str(num).zfill(2) for num in c)
print(str(s))
bg.append(s)
print(bg)
num = 1
P = []
for z in range(num):
P.clear()
for j in range(db * cb):
# 生成有效的数独棋盘
board1 = generate_valid_sudoku(hs)
def create_board2():
board2 = copy.deepcopy(board1)
# 使用empty_counts[kk]获取当前关卡的空缺数
num_blanks = empty_counts[kk]
# 使用新方法生成不连续的空格
blanks = generate_non_adjacent_blanks(hs, num_blanks)
for i in blanks:
row = i // hs
col = i % hs
board2[row][col] = 0
return board2
v1 = board1
v2 = create_board2()
v = v1 + v2
print(v)
P = ['' if a2 == 0 else a2 for a1 in v for a2 in a1]
print(P)
print(len(P))
half_len = int(len(P) // 2)
Q = [P[i:i+half_len] for i in range(0, len(P), half_len)]
print(Q)
time.sleep(3)
print('------答案卡和题目卡------')
title = ['答案卡', '任务卡']
imagePath1 = path + r'\答案卡1'
imagePath2 = path + r'\任务卡1'
os.makedirs(imagePath1, exist_ok=True)
os.makedirs(imagePath2, exist_ok=True)
ti = [path + r'\答案卡1', path + r'\任务卡1']
for tt in range(len(Q)):
doc = Document(path + fr'\动物数独(横板{hs}宫格).docx')
table = doc.tables[0]
for b1 in range(0, 1):
cell_00 = table.cell(0, b1)
cell_00_paragraph = cell_00.paragraphs[0]
cell_00_paragraph.text = f"动物数独 {hs}*{hs} 第 {kk+1} 关 {title[tt]}"
cell_00_paragraph.style.font.bold = True
cell_00_paragraph.style.font.size = Pt(24)
cell_00_paragraph.style.font.name = "黑体"
cell_00_paragraph.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
table = doc.tables[1]
for t in range(0, len(bg)):
pp = int(bg[t][0:2])
qq = int(bg[t][2:4])
k = str(Q[tt][t])
print(pp, qq, k)
run = table.cell(pp, qq).paragraphs[0].add_run(k)
run.font.name = '黑体'
run.font.size = Pt(size)
run.font.color.rgb = RGBColor(0, 0, 0)
run.bold = True
r = run._element
r.rPr.rFonts.set(qn('w:eastAsia'), '黑体')
table.cell(pp, qq).paragraphs[0].alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
doc.save(ti[tt] + fr'\{z+1:02d}.docx')
time.sleep(3)
print('------4、每张word转为pdf----')
from docx import Document
from docx.shared import Cm
animal_path = path + r'\02动物图片'
file_paths = [os.path.join(animal_path, file_name) for file_name in os.listdir(animal_path)]
print(file_paths)
file_paths = file_paths[start:end]
doc = Document(ti[tt] + fr'\{z+1:02d}.docx')
tables = doc.tables
for table in tables:
for i, row in enumerate(table.rows):
for j, cell in enumerate(row.cells):
cell_text = cell.text
for x in range(0, hs):
if cell_text == f'{x+1}':
cell.text = ''
run = cell.paragraphs[0].add_run()
for n in range(len(gzsgg)):
if hs == gzsgg[n]:
run.add_picture(file_paths[x], width=Cm(gzsize[n]), height=Cm(gzsize[n]))
run.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
cell.vertical_alignment = WD_ALIGN_PARAGRAPH.CENTER
cell.paragraphs[0].alignment = WD_ALIGN_PARAGRAPH.CENTER
doc.save(ti[tt] + fr'\{z+1:02d}.docx')
time.sleep(3)
from docx2pdf import convert
inputFile = ti[tt] + fr'\{z+1:02d}.docx'
outputFile = ti[tt] + fr'\{z+1:02d}.pdf'
f1 = open(outputFile, 'w')
f1.close()
convert(inputFile, outputFile)
time.sleep(3)
print('----------更改pdf新名字------------')
tii = [path + r'\06答案卡', path + r'\07任务卡']
for t3 in range(len(tii)):
pdf_lst = [f for f in os.listdir(ti[t3]) if f.endswith('.pdf')]
pdf_path = os.path.join(ti[t3], pdf_lst[0])
print(pdf_path)
# 修改这里 - 使用empty_counts[kk]而不是w[kk]
new_file_name = f"{title[t3]} 动物拼图{hs}宫格 难度{kk+1:02d} 空{empty_counts[kk]:03d}格({db}乘{cb}等于{num}份{db*cb}张).pdf"
output_path = os.path.join(tii[t3], new_file_name)
print(output_path)
os.rename(pdf_path, output_path)
time.sleep(3)
shutil.rmtree(ti[t3])
time.sleep(3)
print('----------第4步:把都有16管PDF关卡变png------------')
li = [path + r'\06答案卡', path + r'\07任务卡']
for l in li:
for root, dirs, files in os.walk(l):
for file in files:
if file.endswith('.pdf'):
filename = os.path.abspath(root + "\\" + file)
print(filename)
pdf = fitz.open(filename)
for pg in range(0, pdf.pageCount):
page = pdf[pg]
trans = fitz.Matrix(2, 2).preRotate(0)
pm = page.getPixmap(matrix=trans, alpha=True)
pm.writePNG(filename.replace('.pdf', '') + str(pg+1) + ".png")
pdf.close()
for parent, dirnames, filenames in os.walk(l):
for fn in filenames:
if fn.lower().endswith('.pdf'):
os.remove(os.path.join(parent, fn))
print('-----第三板块、把动物任务卡和答案卡一套分别做成1页1份,1页2份,1页6份的pdf-------')
psize = [9.39, 9.39, 14.1, 14.1, 19.6, 19.6]
pg = [6, 6, 2, 2, 1, 1]
gzs = [2, 2, 2, 2, 1, 1]
nh = [1, 2, 1, 2, 1, 2]
for x in range(len(pg)):
imagePath11 = path + r'\08答案卡pdf'
imagePath22 = path + r'\09任务卡pdf'
os.makedirs(imagePath11, exist_ok=True)
os.makedirs(imagePath22, exist_ok=True)
tiii = [path + r'\08答案卡pdf', path + r'\09任务卡pdf', path + r'\08答案卡pdf',
path + r'\09任务卡pdf', path + r'\08答案卡pdf', path + r'\09任务卡pdf']
tii = [path + r'\06答案卡', path + r'\07任务卡', path + r'\06答案卡',
path + r'\07任务卡', path + r'\06答案卡', path + r'\07任务卡']
title = ['答案卡', '任务卡', '答案卡', '任务卡', '答案卡', '任务卡']
imagePath = path + r'\\零时Word'
os.makedirs(imagePath, exist_ok=True)
doc = Document(path + fr'\动物数独({pg[x]}图关卡).docx')
tables = doc.tables[0]
file_paths = [os.path.join(tii[x], file) for file in os.listdir(tii[x])]
pic_list_six = [file_paths[i:i+int(pg[x])] for i in range(0, len(file_paths), int(pg[x]))]
print(pic_list_six)
print(len(pic_list_six))
for group_index, group in enumerate(pic_list_six):
doc = Document(path + fr'\动物数独({pg[x]}图关卡).docx')
for cell_index, image_file in enumerate(group):
cc = float(psize[x])
table = doc.tables[0]
cell = table.cell(int(cell_index / gzs[x]), cell_index % gzs[x])
cell_paragraph = cell.paragraphs[0]
cell_paragraph.clear()
run = cell_paragraph.add_run()
run.add_picture(os.path.join(image_file), width=Cm(cc), height=Cm(cc))
cell_paragraph.alignment = 1
doc.save(imagePath + fr'\{group_index + 1:02d}页.docx')
inputFile = imagePath + fr'\{group_index + 1:02d}页.docx'
outputFile = imagePath + fr'\{group_index + 1:02d}页.pdf'
convert(inputFile, outputFile)
time.sleep(3)
pdf_lst = [f for f in os.listdir(imagePath) if f.endswith('.pdf')]
pdf_lst = [os.path.join(imagePath, filename) for filename in pdf_lst]
pdf_lst.sort()
file_merger = PdfMerger()
for pdf in pdf_lst:
print(pdf)
file_merger.append(pdf)
file_merger.write(tiii[x] + fr"\{nh[x]:02} {pg[x]:02}图{title[x]}.pdf")
time.sleep(1)
file_merger.close()
shutil.rmtree(imagePath)
print('-----第四板块、交叉合并有答案6套,无答案6套(1-1-1、1-1,1-2-2,1-2,2-1-1,2-1......----')
newgz = path + fr'\(打印)动物数独{hs}宫格24套 共{len(empty_counts)}关'
os.makedirs(newgz, exist_ok=True)
address = []
all = ['09任务卡pdf', '05操作卡pdf']
for y in all:
new = path + fr'\{y}'
file_new = [os.path.join(new, f) for f in os.listdir(new)]
address.append(file_new)
combinations = [[x, y] for x in address[0] for y in address[1]]
print(combinations)
for files in combinations:
print(files)
pdf_merger = PdfFileMerger()
for file in files:
pdf_merger.append(file)
output_file = newgz + fr"\动物数独{hs}宫格(无答案) 共{len(empty_counts)}关 {files[0][-9:-4]} {files[1][-13:-4]}.pdf"
pdf_merger.write(output_file)
pdf_merger.close()
address = []
all = ['08答案卡pdf', '09任务卡pdf', '05操作卡pdf']
for y in all:
new = path + fr'\{y}'
file_new = [os.path.join(new, f) for f in os.listdir(new)]
address.append(file_new)
combinations = []
for i in address[0]:
for j in address[1]:
for k in address[2]:
combinations.append([i, j, k])
for files in combinations:
print(files)
pdf_merger = PdfFileMerger()
for file in files:
pdf_merger.append(file)
output_file = newgz + fr"\动物数独{hs}宫格(有答案) 共{len(empty_counts)}关 {files[0][-9:-4]} {files[1][-9:-4]} {files[2][-13:-4]}.pdf"
pdf_merger.write(output_file)
pdf_merger.close()
import os
import shutil
for file_name in os.listdir(newgz):
if file_name.endswith(".pdf"):
if f"动物数独{hs}宫格(有答案)" in file_name:
if hs<10:
# 动物数独3宫格(有答案) 共5关 1图答案卡 1图任务卡 0图关卡图操作卡片
if file_name[17] == file_name[23]:
print(f"保留文件:{file_name}")
elif file_name[18] == file_name[24]:
print(f"保留文件:{file_name}")
else:
os.remove(os.path.join(newgz, file_name))
print(f"删除文件:{file_name}")
# 清理临时文件夹
imagePath11 = path + r'\08答案卡pdf'
imagePath22 = path + r'\09任务卡pdf'
imagePatha = path + r'\06答案卡'
imagePathq = path + r'\07任务卡'
imagePathpaste = path + r'\05操作卡pdf'
shutil.rmtree(imagePath11)
shutil.rmtree(imagePath22)
shutil.rmtree(imagePatha)
shutil.rmtree(imagePathq)
shutil.rmtree(imagePathpaste)
end_time = datetime.now()
elapsed_time = end_time - start_time
print(f"动物数独{hs}宫格程序开始时间:{start_time}")
print(f"动物数独{hs}宫格程序结束时间:{end_time}")
print("程序运行时间:", elapsed_time)