シェルスクリプト:数独ゲーム

 数独(Sudoku)は、縦横9×9のマス目に1から9までの数字を入れていくパズルゲームです。縦・横・各3×3のブロック内に同じ数字が入らないように埋めていくのがルールです。ここでは、シェルスクリプトを使って数独ゲームを作成し、その手順と詳細な解説を行います。シェルスクリプトでゲームを作成することで、配列の操作、条件分岐、ループ、ユーザー入力の処理などのスキルを磨くことができます。

数独ゲームの概要

ゲームの目的

  • 縦・横・各ブロックに1から9までの数字が重複しないようにマスを埋めていき、全てのマスを正しく完成させることを目指します。

ゲームのルール

  1. ゲームの進行
    ・9×9のマス目に一部の数字が初期配置されています。
    ・プレイヤーは空いているマスに1から9までの数字を入れていきます。
  2. 数字の配置ルール
    ・各行(横列)に1から9までの数字が重複せずに入る。
    ・各列(縦列)に1から9までの数字が重複せずに入る。
    ・各3×3のブロック(全9ブロック)に1から9までの数字が重複せずに入る。
  3. 勝利条件
    ・全てのマスが正しく埋められ、上記のルールを満たすとゲームクリアとなります。

sudoku.shの作成から実行までの手順と解説

ステップ1:スクリプトファイルの作成

ターミナルで以下のコマンドを実行し、新しいスクリプトファイルsudoku.shを作成します。

user01@ubuntu:~$ nano sudoku.sh

ステップ2:スクリプトの内容を記述

エディタが開いたら、以下のコードをコピーして貼り付けます。

#!/bin/bash

# 初期ボードの定義(0は空きマス)
board=(
    5 3 0 0 7 0 0 0 0
    6 0 0 1 9 5 0 0 0
    0 9 8 0 0 0 0 6 0
    8 0 0 0 6 0 0 0 3
    4 0 0 8 0 3 0 0 1
    7 0 0 0 2 0 0 0 6
    0 6 0 0 0 0 2 8 0
    0 0 0 4 1 9 0 0 5
    0 0 0 0 8 0 0 7 9
)

# ボードの表示関数
display_board() {
    echo "    0 1 2   3 4 5   6 7 8"
    for ((i=0; i<9; i++)); do
        if (( i % 3 == 0 )); then
            echo " -------------------------"
        fi
        echo -n "$i "
        for ((j=0; j<9; j++)); do
            if (( j % 3 == 0 )); then
                echo -n "| "
            fi
            value=${board[$((i * 9 + j))]}
            if [ "$value" -eq 0 ]; then
                echo -n ". "
            else
                echo -n "$value "
            fi
        done
        echo "|"
    done
    echo " -------------------------"
}

# 入力の検証関数
is_valid_move() {
    local row=$1
    local col=$2
    local num=$3

    # 行と列の検証
    for ((k=0; k<9; k++)); do
        if [ "${board[$((row * 9 + k))]}" -eq "$num" ]; then
            return 1
        fi
        if [ "${board[$((k * 9 + col))]}" -eq "$num" ]; then
            return 1
        fi
    done

    # ブロックの検証
    local start_row=$(( (row / 3) * 3 ))
    local start_col=$(( (col / 3) * 3 ))
    for ((i=0; i<3; i++)); do
        for ((j=0; j<3; j++)); do
            if [ "${board[$(((start_row + i) * 9 + start_col + j))]}" -eq "$num" ]; then
                return 1
            fi
        done
    done

    return 0
}

# ゲームのメインループ
echo "=== 数独ゲームへようこそ! ==="
while true; do
    display_board
    read -p "行番号(0-8)を入力してください(終了するにはqを入力): " row
    if [ "$row" == "q" ]; then
        echo "ゲームを終了します。"
        exit 0
    fi
    read -p "列番号(0-8)を入力してください: " col
    read -p "数字(1-9)を入力してください: " num

    # 入力のバリデーション
    if ! [[ "$row" =~ ^[0-8]$ ]] || ! [[ "$col" =~ ^[0-8]$ ]] || ! [[ "$num" =~ ^[1-9]$ ]]; then
        echo "無効な入力です。0-8の行・列番号と1-9の数字を入力してください。"
        continue
    fi

    index=$((row * 9 + col))
    if [ "${board[$index]}" -ne 0 ]; then
        echo "そのマスには既に数字が入っています。"
        continue
    fi

    is_valid_move "$row" "$col" "$num"
    if [ $? -eq 0 ]; then
        board[$index]=$num
    else
        echo "その数字は置けません。ルールを確認してください。"
    fi

    # ゲームクリアのチェック
    is_complete=true
    for cell in "${board[@]}"; do
        if [ "$cell" -eq 0 ]; then
            is_complete=false
            break
        fi
    done

    if $is_complete; then
        display_board
        echo "おめでとうございます!数独を完成させました!"
        exit 0
    fi
done

ステップ3:スクリプトの内容の解説

1.シェバン行

#!/bin/bash
  • 目的:スクリプトがBashシェルで実行されることを指定します。

2.初期ボードの定義

board=(
    5 3 0 0 7 0 0 0 0
    6 0 0 1 9 5 0 0 0
    0 9 8 0 0 0 0 6 0
    8 0 0 0 6 0 0 0 3
    4 0 0 8 0 3 0 0 1
    7 0 0 0 2 0 0 0 6
    0 6 0 0 0 0 2 8 0
    0 0 0 4 1 9 0 0 5
    0 0 0 0 8 0 0 7 9
)
  • 目的:9×9の数独ボードを一次元配列で定義します。0は空きマスを表します。
  • 解説:ボードは81要素の配列で、行と列を計算する際にインデックスを使用します。

3.ボードの表示関数 display_board

display_board() {
    echo "    0 1 2   3 4 5   6 7 8"
    for ((i=0; i<9; i++)); do
        if (( i % 3 == 0 )); then
            echo " -------------------------"
        fi
        echo -n "$i "
        for ((j=0; j<9; j++)); do
            if (( j % 3 == 0 )); then
                echo -n "| "
            fi
            value=${board[$((i * 9 + j))]}
            if [ "$value" -eq 0 ]; then
                echo -n ". "
            else
                echo -n "$value "
            fi
        done
        echo "|"
    done
    echo " -------------------------"
}
  • 目的:現在のボードの状態を表示します。
  • 解説
    ・行と列の番号を表示し、3×3のブロックごとに線を引いて見やすくしています。
    ・空きマスは.で表示されます。

4.入力の検証関数 is_valid_move

is_valid_move() {
    local row=$1
    local col=$2
    local num=$3

    # 行と列の検証
    for ((k=0; k<9; k++)); do
        if [ "${board[$((row * 9 + k))]}" -eq "$num" ]; then
            return 1
        fi
        if [ "${board[$((k * 9 + col))]}" -eq "$num" ]; then
            return 1
        fi
    done

    # ブロックの検証
    local start_row=$(( (row / 3) * 3 ))
    local start_col=$(( (col / 3) * 3 ))
    for ((i=0; i<3; i++)); do
        for ((j=0; j<3; j++)); do
            if [ "${board[$(((start_row + i) * 9 + start_col + j))]}" -eq "$num" ]; then
                return 1
            fi
        done
    done

    return 0
}
  • 目的:指定された位置に指定された数字を置くことが可能かをチェックします。
  • 解説
    行の検証:同じ行に同じ数字がないかを確認します。
    列の検証:同じ列に同じ数字がないかを確認します。
    ブロックの検証:同じ3×3のブロックに同じ数字がないかを確認します。
    ・条件に違反する場合はreturn 1(無効な手)、問題なければreturn 0(有効な手)を返します。

5.ゲームのメインループ

echo "=== 数独ゲームへようこそ! ==="
while true; do
    display_board
    read -p "行番号(0-8)を入力してください(終了するにはqを入力): " row
    if [ "$row" == "q" ]; then
        echo "ゲームを終了します。"
        exit 0
    fi
    read -p "列番号(0-8)を入力してください: " col
    read -p "数字(1-9)を入力してください: " num

    # 入力のバリデーション
    if ! [[ "$row" =~ ^[0-8]$ ]] || ! [[ "$col" =~ ^[0-8]$ ]] || ! [[ "$num" =~ ^[1-9]$ ]]; then
        echo "無効な入力です。0-8の行・列番号と1-9の数字を入力してください。"
        continue
    fi

    index=$((row * 9 + col))
    if [ "${board[$index]}" -ne 0 ]; then
        echo "そのマスには既に数字が入っています。"
        continue
    fi

    is_valid_move "$row" "$col" "$num"
    if [ $? -eq 0 ]; then
        board[$index]=$num
    else
        echo "その数字は置けません。ルールを確認してください。"
    fi

    # ゲームクリアのチェック
    is_complete=true
    for cell in "${board[@]}"; do
        if [ "$cell" -eq 0 ]; then
            is_complete=false
            break
        fi
    done

    if $is_complete; then
        display_board
        echo "おめでとうございます!数独を完成させました!"
        exit 0
    fi
done

解説

  • ユーザーから行番号、列番号、数字を入力してもらいます。
  • 入力のバリデーションを行い、不正な入力の場合は再度入力を求めます。
  • 指定したマスが空きマスであり、is_valid_move関数で有効な手であれば、数字を配置します。
  • 毎ターンごとにゲームクリアのチェックを行い、全てのマスが埋まっていればゲームを終了します。

ステップ4:スクリプトに実行権限を付与

スクリプトを保存してエディタを終了したら、以下のコマンドで実行権限を付与します。

user01@ubuntu:~$ chmod +x sudoku.sh

ステップ5:スクリプトの実行

スクリプトを実行して、数独ゲームを開始します。

user01@ubuntu:~$ ./sudoku.sh

実行例

=== 数独ゲームへようこそ! ===
    0 1 2   3 4 5   6 7 8
 -------------------------
0 | 5 3 . | . 7 . | . . . |
1 | 6 . . | 1 9 5 | . . . |
2 | . 9 8 | . . . | . 6 . |
 -------------------------
3 | 8 . . | . 6 . | . . 3 |
4 | 4 . . | 8 . 3 | . . 1 |
5 | 7 . . | . 2 . | . . 6 |
 -------------------------
6 | . 6 . | . . . | 2 8 . |
7 | . . . | 4 1 9 | . . 5 |
8 | . . . | . 8 . | . 7 9 |
 -------------------------
行番号(0-8)を入力してください(終了するにはqを入力):

解説

  • ボードが表示され、ユーザーは行番号、列番号、数字を順に入力します。
  • 正しい入力であればボードに数字が反映されます。
  • 全てのマスを正しく埋めるとゲームクリアとなります。

ここで作成したファイルの削除

ゲームのテストが終了したら、作成したsudo.shファイルを削除します。

user01@ubuntu:~$ rm sudoku.sh

まとめ

  • シェルスクリプトの応用:数独ゲームを通じて、シェルスクリプトでのゲーム開発手法を学びました。
  • 配列の操作:一次元配列を使用して二次元のボードを管理し、効率的なデータ操作を行いました。
  • 関数の活用display_boardis_valid_moveなどの関数を定義し、コードの再利用性と可読性を向上させました。
  • 条件分岐とループif文やforループを駆使して、ゲームのロジックとフローを制御しました。
  • ユーザー入力の処理readコマンドでユーザーからの入力を取得し、入力のバリデーションを行いました。
  • ゲームロジックの実装:数独のルールに基づく検証機能を実装し、ゲーム性を高めました。

 シェルスクリプトはシステム管理だけでなく、さまざまな用途で活用できます。今回の数独ゲームを基に、難易度選択機能を追加したり、自動解答機能を実装したりして、さらなる機能拡張に挑戦してみてください。