ナンプレ(数独)大好きな筆者 taoですが、かねてから自分でナンプレ問題を作りたいと思っていました。
大胆にも、Python初心者ですが、いきなりコレでやってみようかと(笑)。
とりあえず、まあまあなものが出来たので、作成問題を提示しながら、プログラムを改善してくつもりです。
初心者で、かつ、怖い物知らずなので(笑)、その改善プロセスを公開しちゃうぞ!と思った次第です。

私的な背景やプログラムの仕様など
ナンプレについては、いまから15年前くらいからハマってます。
これまでに解いた問題は、3万問以上になると思います。
それでは、超々難問も、分けなくクリアできる力があるかというと…そうでもない。
さて、1年前に無謀にもPythonで「ナンプレ問題作成プログラム」に着手。
ネットで情報を集め、ほぼコピペ状態で、ver 0.01が出来ましたが、その後は手つかず。
そして、突然、2024年9月1日に「もう一度、チャレンジ!」となった次第。
いつでも、どこでもプログラム作成出来る体制ということで、iPadに「Pythonista 3」(有料 1,500円)を導入。
これMacにもインストールできるので、デスクトップとしても、「Pythonista 3」で動かすことに。
□ □ □
Pythonは初心者なので、実際に、ベースとなるプログラムは「ChatGPT 4o」の支援を受けました。
生成したプログラムで、出題・解答をやらせて、ああじゃない、こうじゃないとフィードバックして修正を重ねる。
そういうことをしばらく続けました。
で、現在は、ver 0.4。
プログラム仕様
かなり未来の仕様として、スマホで遊べるゲームまでに昇華させたいけど、今の能力では無理。
なので、テキストベースで問題を出力という感じの仕様になっています。
- Ver 0.01(一年前)
- とりあえず問題を作るという単純なものを作成
- ver 0.1(2024/9/1)
- ChatGPTで、「とりあえず問題を作る」プログラムのベースを作る
- ver 0.02(2024/9/1)
- 答えが複数ある問題を、解答は一意なものに修正(したつもり)
- ver 0.03(2024/9/1)
- 作成問題について、解答を導き出すプログラムもセットとして作成
- 合わせて、本体のほうは、難易度として「初級」「中級」「上級」「難問」で生成を選べるように修正
- ver 0.04(2024/9/1)
- 作成した問題をについて、解答するというこの2つのプログラムを合体させる
ver 0.4のコード
以下、「ナンプレ問題作成・tao版(ver 0.4)」を載せます。
ただし、問題が…。
このブログ、全体的にコピープロテクトを掛けています。
そして、特定ページ(例えば、このページ)だけコピープロテクトをオフにする方法が分からない。
なので、以下のコード、コピーできません(なんとか方法を考えますが…)。
とりあえず、コード載せます。
import random
# 関数の再定義(すべての必要な関数をインポート)
def is_valid(board, row, col, num):
for i in range(9):
if board[row][i] == num or board[i][col] == num:
return False
start_row, start_col = 3 * (row // 3), 3 * (col // 3)
for i in range(start_row, start_row + 3):
for j in range(start_col, start_col + 3):
if board[i][j] == num:
return False
return True
def solve_sudoku(board):
for row in range(9):
for col in range(9):
if board[row][col] == 0:
for num in range(1, 10):
if is_valid(board, row, col, num):
board[row][col] = num
if solve_sudoku(board):
return True
board[row][col] = 0
return False
return True
def count_solutions(board):
def solve(board):
nonlocal count
for row in range(9):
for col in range(9):
if board[row][col] == 0:
for num in range(1, 10):
if is_valid(board, row, col, num):
board[row][col] = num
solve(board)
board[row][col] = 0
return
count += 1
count = 0
solve(board)
return count
def remove_numbers_strict(board, holes):
attempts = holes
while attempts > 0:
row, col = random.randint(0, 8), random.randint(0, 8)
while board[row][col] == 0:
row, col = random.randint(0, 8), random.randint(0, 8)
backup = board[row][col]
board[row][col] = 0
board_copy = [row[:] for row in board]
if count_solutions(board_copy) != 1:
board[row][col] = backup
else:
attempts -= 1
return board
def generate_complete_board_backtracking():
def fill_board(board):
for i in range(9):
for j in range(9):
if board[i][j] == 0:
random_nums = list(range(1, 10))
random.shuffle(random_nums)
for num in random_nums:
if is_valid(board, i, j, num):
board[i][j] = num
if fill_board(board):
return True
board[i][j] = 0
return False
return True
board = [[0] * 9 for _ in range(9)]
fill_board(board)
return board
def generate_sudoku_strict(difficulty):
complete_board = generate_complete_board_backtracking()
puzzle = remove_numbers_strict(complete_board, difficulty)
return puzzle
def print_sudoku(board):
print("+-------+-------+-------+")
for i in range(9):
row = "| "
for j in range(9):
if board[i][j] == 0:
row += " "
else:
row += str(board[i][j]) + " "
if (j + 1) % 3 == 0:
row += "| "
print(row)
if (i + 1) % 3 == 0:
print("+-------+-------+-------+")
def solve_sudoku_and_print(board):
def solve(board):
for row in range(9):
for col in range(9):
if board[row][col] == 0:
for num in range(1, 10):
if is_valid(board, row, col, num):
board[row][col] = num
if solve(board):
return True
board[row][col] = 0
return False
return True
if solve(board):
print_sudoku(board)
else:
print("解けない")
# 自動化プロセスの関数
def sudoku_solver_process(level):
# 難易度に応じた穴の数を設定
if level == "初級":
holes = 30
elif level == "中級":
holes = 40
elif level == "上級":
holes = 50
elif level == "難問":
holes = 55
else:
print("無効なレベルです。レベルは「初級」「中級」「上級」「難問」から選んでください。")
return
print(f"\n{level}レベルのナンプレ問題:")
puzzle = generate_sudoku_strict(holes)
print_sudoku(puzzle)
print(f"\n{level}レベルのナンプレ問題の解答:")
solve_sudoku_and_print(puzzle)
# 例: 初級レベルのナンプレ問題を生成して解く
sudoku_solver_process("難問")
当面の課題
次の項で、このプログラムで作った問題を4つ載せます。
初級、中級、上級、難問の4つ。
実は、昨夜からこの4つのセットをいくつか作って、検証しています。
現状の「要改善点」として考えている点は次の通り。
- これまで繰り返しの検証で「答えの無い問題」を作ることは回避できるようになった。また、解答が2つ以上ある問題は避けるような機能も入れたが、こちらのほうは、たまに、解答が2つある問題を作ってしまうことがある。(以下にも、そういう問題例を記す)
- 初級・中級・上級がいずれも簡単すぎる
- 初級・中級・上級・難問のレベル差を「問題の空白欄数」以外に、何か数値化できるものはないかを探る
ナンプレ問題作成・tao版(ver 0.4)の問題 001
現状の最新版(ver 0.4)で作成した問題と解答を4つ提示します。解答は、明日の問題提示のところで掲載する予定です。
初級 001


中級 001


上級 001


難問 001
★この「難問 001」は解答が2通りあります。



まとめ
「ナンプレ大好きだけどプログラム素人」の筆者 taoが、大胆にもPythonで「ナンプレ問題作成プログラムを作ってみた」のテーマでスタート。
当面、こつこつ毎日、改良をして、1ヶ月後に「機能はこれで十分」となれば、次は、スマホアプリ作成に移行する予定です。
Python素人にどこまで出来るか。
優しい目で見守ってください。
コメント