Python と Pygame を使った数独ゲームの開発

PythonBeginner
オンラインで実践に進む

はじめに

このプロジェクトでは、Python と Pygame ライブラリを使って数独ゲームを作成します。このゲームでは、指定された難易度の数独のグリッドを生成し、プレイヤーに空のセルに数字を入力してパズルを解かせます。このゲームでは、難易度の選択、選択されたセルの強調表示、グリッドが完成しているかどうかを確認する機能が提供されます。

👀 プレビュー

数独ゲームのプレビュー

🎯 タスク

このプロジェクトでは、以下のことを学びます。

  • 必要なライブラリをインポートする方法
  • PyGame を初期化する方法
  • 色を定義する方法
  • ゲームウィンドウのサイズとタイトルを設定する方法
  • ゲームウィンドウを作成する方法
  • フォントを読み込む方法
  • 数独のグリッドを生成する方法
  • バックトラッキングアルゴリズムを使って数独のグリッドを解く方法
  • 難易度に応じてグリッドから数字を削除する方法
  • ゲームウィンドウに数独のグリッドを描画する方法
  • グリッドが完全に埋まっているかどうかを確認する方法
  • マウスの位置の下のセルの座標を取得する方法
  • 難易度レベルを選択する方法
  • メインゲームループを実装する方法

🏆 成果

このプロジェクトを完了すると、以下のことができるようになります。

  • Python でのゲーム開発に Pygame ライブラリを使用する
  • 指定された難易度の数独のグリッドを生成する
  • バックトラッキングアルゴリズムを使って数独のグリッドを解く
  • Pygame でマウスとキーボードのイベントを処理する
  • ゲームウィンドウに図形とテキストを描画する
  • Pygame でメインゲームループを実装する

プロジェクトファイルを作成する

まず、sudoku_game.py という名前のファイルを作成し、好きなテキストエディタまたは統合開発環境 (IDE) で開きます。

cd ~/project
touch sudoku_game.py

必要なライブラリをインポートする

ファイルの最初に必要なライブラリをインポートします。このゲームには pygamerandom のライブラリが必要です。

import pygame
import random

pip コマンドを使って pygame ライブラリをインストールします。

sudo pip install pygame

PyGame を初期化する

ゲームウィンドウを設定するために、PyGame ライブラリを初期化します。

pygame.init()

色を定義する

ゲームで使用する色を定義します。色には RGB 形式を使用します。

BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (200, 200, 200)
BLUE = (0, 0, 255)
LIGHT_BLUE = (100, 100, 255)

ゲームウィンドウのサイズとタイトルを設定する

ゲームウィンドウのサイズを設定し、ゲームのタイトルを設定します。

WINDOW_SIZE = (550, 550)
CELL_SIZE = WINDOW_SIZE[0] // 9

pygame.display.set_caption("Sudoku Game")

ゲームウィンドウを作成する

指定されたサイズを使ってゲームウィンドウを作成します。

screen = pygame.display.set_mode(WINDOW_SIZE)

フォントを読み込む

ゲーム画面に数字を表示するために、フォントを読み込む必要があります。大きな数字用と小さな数字用の 2 種類のフォントを読み込みます。

font_large = pygame.font.SysFont("calibri", 50)
font_small = pygame.font.SysFont("calibri", 30)

数独グリッドを生成する

指定された難易度の新しい数独のグリッドを生成する関数generate_sudoku(difficulty)を作成します。この関数は生成されたグリッドを返す必要があります。

def generate_sudoku(difficulty):
    ## 関数のコードはここに書きます
    pass

対角線のサブグリッドを埋める

generate_sudoku関数の中で、グリッドの対角線のサブグリッドにランダムな数字を入れます。これにより、各サブグリッドに 1 から 9 までの数字が重複なく含まれるようになります。

## 対角線のサブグリッドに数字を入れる
for i in range(0, 9, 3):
    nums = random.sample(range(1, 10), 3)
    for j in range(3):
        grid[i + j][i + j] = nums[j]

数独グリッドを解く

バックトラッキングアルゴリズムを使って数独のグリッドを解く関数solve_sudoku(grid)を作成します。この関数は、グリッドが解ける場合はTrueを返し、そうでない場合はFalseを返す必要があります。

def solve_sudoku(grid):
    ## 関数のコードはここに書きます
    pass

空のセルを見つける

solve_sudoku関数の中で、グリッド内の次の空のセルの座標を返す補助関数find_empty_cell(grid)を作成します。空のセルがない場合は、Noneを返します。

def find_empty_cell(grid):
    for row in range(9):
        for col in range(9):
            if grid[row][col] == 0:
                return (row, col)

    return None

有効な手を確認する

solve_sudoku関数の中で、セルに数字を配置することが有効な手であるかどうかを確認する補助関数is_valid_move(grid, row, col, num)を作成します。この関数は、その手が有効な場合はTrueを返し、そうでない場合はFalseを返す必要があります。

def is_valid_move(grid, row, col, num):
    ## 関数のコードはここに書きます
    pass

数独グリッドを解く(続き)

solve_sudoku関数の中で、補助関数find_empty_cellis_valid_moveを使ってバックトラッキングアルゴリズムを実装します。解が見つかった場合はTrueを返します。現在の数字が無効な解につながる場合は、現在のセルを 0 に設定してバックトラックします。

## 空のセルに 1 から 9 までの数字を入れてみる
for num in range(1, 10):
    if is_valid_move(grid, row, col, num):
        grid[row][col] = num

        if solve_sudoku(grid):
            return True

        ## 現在の数字が無効な解につながる場合は、バックトラック
        grid[row][col] = 0

return False

難易度に基づいて数字を削除する

generate_sudoku関数の中で、指定された難易度レベルに基づいてグリッドから数字を削除します。難易度レベルは:1(簡単)、2(中等)、3(難しい)です。削除する数字の数はnum_to_remove = 45 + 10 * difficultyとして計算されます。

## 難易度レベルに基づいて数字を削除する
num_to_remove = 45 + 10 * difficulty
for _ in range(num_to_remove):
    row = random.randint(0, 8)
    col = random.randint(0, 8)
    grid[row][col] = 0

return grid

グリッドを描画する

ゲームウィンドウに数独のグリッドを描画する関数draw_grid(grid, selected_cell)を作成します。この関数は、セル、数字の描画、および選択されたセルの強調表示を担当する必要があります。

def draw_grid(grid, selected_cell):
    ## 関数のコードはここに書きます
    pass

セルを埋めて数字を描画する

draw_grid関数の中で、グリッドをループして、Pygame の関数pygame.draw.rectscreen.blitを使ってセルと数字を描画します。

## セルを描画する
for row in range(9):
    for col in range(9):
        cell_rect = pygame.Rect(
            col * CELL_SIZE, row * CELL_SIZE, CELL_SIZE, CELL_SIZE
        )
        pygame.draw.rect(screen, GRAY, cell_rect)

        ## 数字を描画する
        if grid[row][col]!= 0:
            number = font_small.render(str(grid[row][col]), True, BLACK)
            text_rect = number.get_rect(
                center=(
                    col * CELL_SIZE + CELL_SIZE // 2,
                    row * CELL_SIZE + CELL_SIZE // 2,
                )
            )
            screen.blit(number, text_rect)

選択されたセルを強調表示する

draw_grid関数の中で、選択されたセルがNoneでない場合、pygame.draw.rect関数を使ってそのセルを強調表示します。

## 選択されたセルを強調表示する
if (row, col) == selected_cell:
    pygame.draw.rect(screen, LIGHT_BLUE, cell_rect, 3)

グリッド線を描画する

draw_grid関数の中で、pygame.draw.line関数を使ってグリッドを作成するための線を描画します。

## 線を描画する
for i in range(10):
    if i % 3 == 0:
        thickness = 4
    else:
        thickness = 1

    pygame.draw.line(
        screen,
        BLACK,
        (0, i * CELL_SIZE),
        (WINDOW_SIZE[0], i * CELL_SIZE),
        thickness,
    )
    pygame.draw.line(
        screen,
        BLACK,
        (i * CELL_SIZE, 0),
        (i * CELL_SIZE, WINDOW_SIZE[1]),
        thickness,
    )

表示を更新する

draw_grid関数の中で、pygame.display.update関数を使ってディスプレイを更新します。

pygame.display.update()

グリッドがいっぱいかどうかを確認する

数独のグリッドが完全に埋まっているかどうかを確認するis_grid_full(grid)関数を作成します。これは、グリッドをループして、セルに 0 が含まれているかどうかを確認することで行うことができます。

def is_grid_full(grid):
    ## 関数コードはここに記述します
    pass

マウスの下のセルを取得する

マウス位置の下のセル座標を返すget_cell_under_mouse(pos)関数を作成します。これは、マウス位置をセルサイズで割ることによって計算できます。

def get_cell_under_mouse(pos):
    ## 関数コードはここに記述します
    pass

難易度を選択する

ゲームを開始する前にプレイヤーが難易度を選択できるselect_difficulty()関数を作成します。この関数は難易度オプションを表示し、選択された難易度を返す必要があります。

def select_difficulty():
    ## 関数コードはここに記述します
    pass

難易度選択を実装する

select_difficulty関数の中で、Pygame の関数pygame.mouse.get_pospygame.event.getを使って難易度選択のロジックを実装します。

## 難易度選択のテキストを表示する
title_text = font_large.render("Select Difficulty", True, BLACK)
screen.blit(title_text, (110, 200))

## 難易度オプションを表示する
option_y = 300
for difficulty, label in difficulties.items():
    option_text = font_small.render(f"{difficulty}. {label}", True, BLACK)
    text_rect = option_text.get_rect(center=(WINDOW_SIZE[0] // 2, option_y))
    screen.blit(option_text, text_rect)
    option_y += 70

pygame.display.update()

## 難易度選択を待つ
difficulty_selected = False
difficulty = 1

while not difficulty_selected:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            return
        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1:  ## 左マウスボタン
                pos = pygame.mouse.get_pos()
                if 180 <= pos[0] <= 380:
                    if 300 <= pos[1] <= 350:
                        difficulty = 1
                        difficulty_selected = True
                    elif 370 <= pos[1] <= 420:
                        difficulty = 2
                        difficulty_selected = True
                    elif 440 <= pos[1] <= 490:
                        difficulty = 3
                        difficulty_selected = True

return difficulty

メインゲームループ

main()関数を定義することでメインゲームループを作成します。

def main():
    ## 関数コードはここに記述します
    pass

難易度を選択してグリッドを生成する

main関数の中で、select_difficulty()を呼び出して難易度を選択し、難易度がNoneの場合は返します。その後、選択された難易度を使ってgenerate_sudoku(difficulty)を呼び出して数独のグリッドを生成します。

## 難易度を選択する
difficulty = select_difficulty()
if difficulty is None:
    return

## 新しい数独のグリッドを生成する
grid = generate_sudoku(difficulty)

ゲームループ変数

main関数の中で、ゲームループ用の変数selected_cellrunningを作成します。selected_cellNoneに、runningTrueに設定します。

selected_cell = None
running = True

キーリピートを有効にする

main関数の中で、pygame.key.set_repeat関数を使ってキーリピートを有効にします。セルを埋めるのをプレイヤーにしやすくするために、遅延とインターバルを 100 ミリ秒に設定します。

## キーリピートを有効にする
pygame.key.set_repeat(100, 100)

イベントを処理する

ゲームループの中で、pygame.event.get関数を使ってイベントを処理します。終了イベント、マウスボタンイベント、キーイベントをチェックします。

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        running = False
    elif event.type == pygame.MOUSEBUTTONDOWN:
        ## マウスボタンイベントを処理する
        pass
    elif event.type == pygame.KEYDOWN:
        ## キーイベントを処理する
        pass

マウスボタンイベントを処理する

マウスボタンイベントのイベント処理の中で、左マウスボタンがクリックされたか右マウスボタンがクリックされたかをチェックします。

if event.button == 1:  ## 左マウスボタン
    ## 左マウスボタンのクリックを処理する
    pass
elif event.button == 3:  ## 右マウスボタン
    ## 右マウスボタンのクリックを処理する
    pass

左マウスボタンクリックを処理する

左マウスボタンのクリック処理の中で、get_cell_under_mouse(pos)を呼び出すことで、マウス位置の下にあるセル座標を取得します。

## マウス位置の下にあるセルを取得する
pos = pygame.mouse.get_pos()
row, col = get_cell_under_mouse(pos)

左マウスボタンクリックを処理する(続き)

左マウスボタンのクリック処理の中で、grid[row][col] == 0 をチェックすることで、セルが空の場合にそのセルを選択します。

## セルが空の場合に選択する
if grid[row][col] == 0:
    selected_cell = (row, col)

右マウスボタンクリックを処理する

右マウスボタンのクリック処理の中で、選択されたセルが None でなく、かつ grid[selected_cell[0]][selected_cell[1]] == 0 をチェックすることで空の場合にそのセルをクリアします。

if selected_cell:
    ## 選択されたセルが空の場合にクリアする
    if grid[selected_cell[0]][selected_cell[1]] == 0:
        grid[selected_cell[0]][selected_cell[1]] = 0

キーイベントを処理する

キーイベントのイベント処理の中で、どの数字キーが押されたかをチェックし、選択されたセルの値をそれに応じて設定します。

if selected_cell:
    ## 押されたキーを取得する
    if event.key == pygame.K_1:
        grid[selected_cell[0]][selected_cell[1]] = 1
    elif event.key == pygame.K_2:
        grid[selected_cell[0]][selected_cell[1]] = 2
    elif event.key == pygame.K_3:
        grid[selected_cell[0]][selected_cell[1]] = 3
    elif event.key == pygame.K_4:
        grid[selected_cell[0]][selected_cell[1]] = 4
    elif event.key == pygame.K_5:
        grid[selected_cell[0]][selected_cell[1]] = 5
    elif event.key == pygame.K_6:
        grid[selected_cell[0]][selected_cell[1]] = 6
    elif event.key == pygame.K_7:
        grid[selected_cell[0]][selected_cell[1]] = 7
    elif event.key == pygame.K_8:
        grid[selected_cell[0]][selected_cell[1]] = 8
    elif event.key == pygame.K_9:
        grid[selected_cell[0]][selected_cell[1]] = 9

グリッドを描画して完了を確認する

ゲームループの中で、draw_grid(grid, selected_cell) を呼び出してグリッドを描画します。その後、is_grid_full(grid) を呼び出してグリッドが完成しているかどうかを確認します。

## グリッドを描画する
draw_grid(grid, selected_cell)

## グリッドが完成しているかどうかを確認する
if is_grid_full(grid):
    print("Congratulations! You solved the Sudoku puzzle.")

ディスプレイを更新して終了する

ゲームループの後、pygame.display.update 関数を使ってディスプレイを更新し、pygame.quit() を使ってゲームを終了します。

## ディスプレイを更新する
pygame.display.update()

## ゲームを終了する
pygame.quit()

ゲームを実行する

最後に、現在のファイルがプログラムのメインエントリーポイントであるかどうかをチェックする条件を追加します。もしそうであれば、main() 関数を呼び出してゲームを実行します。

if __name__ == "__main__":
    main()

python コマンドを使ってゲームを実行します。

python sudoku_game.py

Sudoku game execution screenshot

まとめ

おめでとうございます!あなたは Python と Pygame ライブラリを使って数独ゲームを成功裏に作成しました。このゲームでは、プレイヤーが難易度を選択し、空のセルに数字を入力し、グリッドが完成しているかどうかを確認できます。数独パズルを楽しんで解いてください!

✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習✨ 解答を確認して練習