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

 シェルスクリプトは、システム管理だけでなく、ゲームの作成にも活用できる強力なツールです。ここでは、シェルスクリプトを使って「ニムゲーム」を作成し、その手順と詳細な解説を行います。この演習を通じて、シェルスクリプトの基本的な構文、配列の操作、条件分岐、ループ、ユーザー入力の処理などの知識を総合的に学ぶことができます。

ニムゲームの概要

ゲームの目的

  • 複数の石(マッチ棒)からなる山から交互に石を取り、最後の石を取らないようにします。

ゲームのルール

  • ゲームの進行
    ・ゲームには複数の山があり、それぞれに一定数の石があります。
    ・プレイヤーとコンピュータは交互にターンを行います。
    ・自分のターンで、プレイヤーは一つの山を選び、そこから1個以上の石を取ります。
    ・一度に複数の山から石を取ることはできません。
  • 勝利条件
    ・最後の石を取ったプレイヤーが負けとなります。

ゲームの流れ

  • 初期設定として、3つの山にそれぞれ3、5、7個の石があります。
  • プレイヤーとコンピュータが交互に石を取っていきます。
  • 戦略的に石を取ることで、相手に最後の石を取らせるようにします。

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

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

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

user01@ubuntu:~$ nano nim_game.sh

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

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

#!/bin/bash

# 山の初期設定
piles=(3 5 7)

# ゲーム開始のメッセージ
echo "=== ニムゲームへようこそ! ==="
echo "最後の石を取らないようにしましょう。"

# 現在の山の状態を表示する関数
display_piles() {
    echo "現在の山の状態:"
    for i in "${!piles[@]}"; do
        echo "山$((i + 1)): ${piles[$i]} 個の石"
    done
}

# プレイヤーのターン
player_turn() {
    while true; do
        display_piles
        read -p "どの山から石を取りますか?(1〜3): " pile_choice
        if ! [[ "$pile_choice" =~ ^[1-3]$ ]]; then
            echo "無効な山の番号です。もう一度入力してください。"
            continue
        fi
        pile_index=$((pile_choice - 1))
        if [ "${piles[$pile_index]}" -eq 0 ]; then
            echo "選択した山には石がありません。別の山を選んでください。"
            continue
        fi
        read -p "いくつの石を取りますか?(1〜${piles[$pile_index]}): " remove_count
        if ! [[ "$remove_count" =~ ^[1-9][0-9]*$ ]]; then
            echo "正の整数を入力してください。"
            continue
        fi
        if [ "$remove_count" -lt 1 ] || [ "$remove_count" -gt "${piles[$pile_index]}" ]; then
            echo "無効な石の数です。もう一度入力してください。"
            continue
        fi
        piles[$pile_index]=$((piles[$pile_index] - remove_count))
        break
    done
}

# コンピュータのターン
computer_turn() {
    echo "コンピュータのターンです。"
    # 単純な戦略:最初に石が残っている山を見つけて1個取る
    for i in "${!piles[@]}"; do
        if [ "${piles[$i]}" -gt 0 ]; then
            echo "コンピュータは山$((i + 1))から1個の石を取った。"
            piles[$i]=$((piles[$i] - 1))
            break
        fi
    done
}

# ゲーム終了の判定
is_game_over() {
    total=0
    for pile in "${piles[@]}"; do
        total=$((total + pile))
    done
    if [ "$total" -eq 0 ]; then
        return 0  # ゲームオーバー
    else
        return 1  # ゲーム続行
    fi
}

# メインループ
while true; do
    # プレイヤーのターン
    player_turn
    if is_game_over; then
        echo "あなたが最後の石を取りました。あなたの負けです。"
        exit
    fi

    # コンピュータのターン
    computer_turn
    if is_game_over; then
        echo "コンピュータが最後の石を取りました。あなたの勝ちです!"
        exit
    fi
done

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

1.シェバン行

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

2.山の初期設定

piles=(3 5 7)
  • 目的:3つの山にそれぞれ3、5、7個の石を配置します。

3.ゲーム開始のメッセージ

echo "=== ニムゲームへようこそ! ==="
echo "最後の石を取らないようにしましょう。"
  • ゲーム開始の挨拶とルールの簡単な説明を表示します。

4.山の状態を表示する関数 display_piles

display_piles() {
    echo "現在の山の状態:"
    for i in "${!piles[@]}"; do
        echo "山$((i + 1)): ${piles[$i]} 個の石"
    done
}
  • 目的:現在の各山の石の数を表示します。
  • 解説
    for ループで各山を巡回し、山の番号と石の数を表示します。

5.プレイヤーのターン関数 player_turn

player_turn() {
    while true; do
        display_piles
        read -p "どの山から石を取りますか?(1〜3): " pile_choice
        if ! [[ "$pile_choice" =~ ^[1-3]$ ]]; then
            echo "無効な山の番号です。もう一度入力してください。"
            continue
        fi
        pile_index=$((pile_choice - 1))
        if [ "${piles[$pile_index]}" -eq 0 ]; then
            echo "選択した山には石がありません。別の山を選んでください。"
            continue
        fi
        read -p "いくつの石を取りますか?(1〜${piles[$pile_index]}): " remove_count
        if ! [[ "$remove_count" =~ ^[1-9][0-9]*$ ]]; then
            echo "正の整数を入力してください。"
            continue
        fi
        if [ "$remove_count" -lt 1 ] || [ "$remove_count" -gt "${piles[$pile_index]}" ]; then
            echo "無効な石の数です。もう一度入力してください。"
            continue
        fi
        piles[$pile_index]=$((piles[$pile_index] - remove_count))
        break
    done
}
  • 目的:プレイヤーが石を取る処理を行います。
  • 解説
    ・山の番号と石の数を入力してもらい、入力のバリデーションを行います。
    ・入力が正しければ、指定された山から石を減らします。

6.コンピュータのターン関数 computer_turn

computer_turn() {
    echo "コンピュータのターンです。"
    # 単純な戦略:最初に石が残っている山を見つけて1個取る
    for i in "${!piles[@]}"; do
        if [ "${piles[$i]}" -gt 0 ]; then
            echo "コンピュータは山$((i + 1))から1個の石を取った。"
            piles[$i]=$((piles[$i] - 1))
            break
        fi
    done
}
  • 目的:コンピュータが石を取る処理を行います。
  • 解説
    ・単純な戦略として、最初に見つけた石が残っている山から1個の石を取ります。

7.ゲーム終了の判定関数 is_game_over

is_game_over() {
    total=0
    for pile in "${piles[@]}"; do
        total=$((total + pile))
    done
    if [ "$total" -eq 0 ]; then
        return 0  # ゲームオーバー
    else
        return 1  # ゲーム続行
    fi
}
  • 目的:全ての山から石がなくなったかをチェックします。
  • 解説
    ・すべての山の石の数を合計し、合計が0ならゲームオーバーとします。

8.メインループ

while true; do
    # プレイヤーのターン
    player_turn
    if is_game_over; then
        echo "あなたが最後の石を取りました。あなたの負けです。"
        exit
    fi

    # コンピュータのターン
    computer_turn
    if is_game_over; then
        echo "コンピュータが最後の石を取りました。あなたの勝ちです!"
        exit
    fi
done

解説

  • プレイヤーとコンピュータのターンを交互に行います。
  • 各ターンの後にゲームオーバーのチェックを行い、ゲーム終了時には勝敗を表示してプログラムを終了します。

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

スクリプトを保存してエディタを終了したら、実行権限を付与します。

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

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

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

user01@ubuntu:~$ ./nim_game.sh

実行例

=== ニムゲームへようこそ! ===
最後の石を取らないようにしましょう。
現在の山の状態:
山1: 3 個の石
山2: 5 個の石
山3: 7 個の石
どの山から石を取りますか?(1〜3): 3
いくつの石を取りますか?(1〜7): 2

コンピュータのターンです。
コンピュータは山1から1個の石を取った。

現在の山の状態:
山1: 2 個の石
山2: 5 個の石
山3: 5 個の石
どの山から石を取りますか?(1〜3): 2
いくつの石を取りますか?(1〜5): 3

コンピュータのターンです。
コンピュータは山1から1個の石を取った。

現在の山の状態:
山1: 1 個の石
山2: 2 個の石
山3: 5 個の石
どの山から石を取りますか?(1〜3): 3
いくつの石を取りますか?(1〜5): 5

コンピュータのターンです。
コンピュータは山1から1個の石を取った。
コンピュータが最後の石を取りました。あなたの勝ちです!

解説

  • プレイヤーは山と取る石の数を入力して、石を取ります。
  • コンピュータは単純な戦略で石を取ります。
  • 最後の石をコンピュータが取ったので、プレイヤーの勝ちとなります。

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

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

user01@ubuntu:~$ rm nim_game.sh

まとめ

  • シェルスクリプトの応用:ニムゲームを通じて、シェルスクリプトでのゲーム作成方法を学びました。
  • 配列の操作:山の状態を配列で管理し、効率的にデータを扱いました。
  • 条件分岐とループif 文や while ループを使って、ユーザー入力のバリデーションやゲームの進行を制御しました。
  • ユーザー入力の処理read コマンドを使用してユーザーの入力を受け取り、正しい形式かをチェックしました。
  • 関数の定義と利用:再利用可能なコードを関数として定義し、コードの可読性と保守性を向上させました。

 シェルスクリプトは、システム管理以外にも多くの用途で活用できます。今回のニムゲームを基に、コンピュータの戦略を高度化したり、山の数や石の数を変更できるようにしたり、2人のプレイヤーで対戦できるようにしたりして、さらなる機能拡張に挑戦してみてください。