Python初心者がナンプレ問題作成プログラムを作ってみた-001(プログラムコード有り)

  • URLをコピーしました!
 *本記事を含め、当サイトでは広告を掲載しています。

ナンプレ(数独)大好きな筆者 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

Screenshot
Screenshot

中級 001

Screenshot
Screenshot

上級 001

Screenshot
Screenshot

難問 001

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

Screenshot
Screenshot
Screenshot

まとめ

「ナンプレ大好きだけどプログラム素人」の筆者 taoが、大胆にもPythonで「ナンプレ問題作成プログラムを作ってみた」のテーマでスタート。

当面、こつこつ毎日、改良をして、1ヶ月後に「機能はこれで十分」となれば、次は、スマホアプリ作成に移行する予定です。

Python素人にどこまで出来るか。

優しい目で見守ってください。

スポンサーリンク

よかったらシェアしてね!
  • URLをコピーしました!
目次