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

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

バトルシップゲームの概要

ゲームの目的

  • 敵の艦隊を見つけて全て撃沈すること。

ゲームのルール

  • ボードの設定
    ・5×5のグリッドを使用します。
    ・敵の艦隊はランダムに配置された3隻の船から構成されます。
     ・巡洋艦(C):サイズ1
     ・潜水艦(S):サイズ1
     ・駆逐艦(D):サイズ1
  • ゲームの進行
    ・プレイヤーは座標を指定して砲撃します(例:A1, B3)。
    ・命中した場合は「命中」、外れた場合は「外れ」と表示されます。
    ・全ての船を撃沈するとゲームクリアです。

ボードの例

12345
A
B
C
D
E

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

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

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

user01@ubuntu:~$ nano battleship.sh

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

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

#!/bin/bash

# ボードの初期化
board=()
for ((i=0; i<25; i++)); do
    board[$i]="~"
done

# 船の配置
place_ships() {
    ships=("C" "S" "D")
    for ship in "${ships[@]}"; do
        while true; do
            position=$((RANDOM % 25))
            if [ "${board[$position]}" == "~" ]; then
                board[$position]=$ship
                break
            fi
        done
    done
}

# ボードの表示(プレイヤー用)
display_board() {
    echo "  1 2 3 4 5"
    rows=("A" "B" "C" "D" "E")
    for i in {0..4}; do
        echo -n "${rows[$i]} "
        for j in {0..4}; do
            index=$((i * 5 + j))
            if [ "${hits[$index]}" == "hit" ]; then
                echo -n "X "
            elif [ "${hits[$index]}" == "miss" ]; then
                echo -n "O "
            else
                echo -n "~ "
            fi
        done
        echo
    done
}

# 座標をインデックスに変換
coordinate_to_index() {
    declare -A row_map=( ["A"]=0 ["B"]=1 ["C"]=2 ["D"]=3 ["E"]=4 )
    row_letter=${1^^}
    col_number=$2
    row=${row_map[$row_letter]}
    col=$((col_number - 1))
    index=$((row * 5 + col))
    echo $index
}

# ゲームの開始
place_ships
hits=()

echo "バトルシップゲームへようこそ!"

# メインゲームループ
while true; do
    display_board
    read -p "砲撃する座標を入力してください(例:A1): " input
    row_input=${input:0:1}
    col_input=${input:1}
    if [[ ! "$row_input" =~ [A-Ea-e] ]] || [[ ! "$col_input" =~ [1-5] ]]; then
        echo "エラー: A1からE5の範囲で入力してください。"
        continue
    fi
    index=$(coordinate_to_index "$row_input" "$col_input")
    if [ "${hits[$index]}" ]; then
        echo "既に砲撃した座標です。別の座標を選んでください。"
        continue
    fi
    if [ "${board[$index]}" != "~" ]; then
        echo "命中!"
        hits[$index]="hit"
        board[$index]="X"
        # 全ての船が撃沈されたかチェック
        remaining_ships=0
        for cell in "${board[@]}"; do
            if [[ "$cell" == "C" || "$cell" == "S" || "$cell" == "D" ]]; then
                remaining_ships=$((remaining_ships + 1))
            fi
        done
        if [ "$remaining_ships" -eq 0 ]; then
            echo "全ての船を撃沈しました!あなたの勝ちです。"
            break
        fi
    else
        echo "外れ!"
        hits[$index]="miss"
    fi
done

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

1.シェバン行

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

2.ボードの初期化

board=()
for ((i=0; i<25; i++)); do
    board[$i]="~"
done
  • 目的:5×5のボードを表す配列 board を初期化します。
  • 解説
    ~ は海を表します。
    ・インデックス0から24までの配列要素を ~ に設定します。

3.船の配置関数 place_ships

place_ships() {
    ships=("C" "S" "D")
    for ship in "${ships[@]}"; do
        while true; do
            position=$((RANDOM % 25))
            if [ "${board[$position]}" == "~" ]; then
                board[$position]=$ship
                break
            fi
        done
    done
}
  • 目的:ランダムに船をボード上に配置します。
  • 解説
    ・船は「C」(巡洋艦)、「S」(潜水艦)、「D」(駆逐艦)の3種類。
    while ループで空いているマス(~)を見つけて船を配置します。

4.ボードの表示関数 display_board

display_board() {
    echo "  1 2 3 4 5"
    rows=("A" "B" "C" "D" "E")
    for i in {0..4}; do
        echo -n "${rows[$i]} "
        for j in {0..4}; do
            index=$((i * 5 + j))
            if [ "${hits[$index]}" == "hit" ]; then
                echo -n "X "
            elif [ "${hits[$index]}" == "miss" ]; then
                echo -n "O "
            else
                echo -n "~ "
            fi
        done
        echo
    done
}
  • 目的:プレイヤーに見えるボードを表示します。
  • 解説
    ・命中箇所は X、外れた箇所は O、未攻撃箇所は ~ で表示します。
    ・行はAからE、列は1から5でラベル付けします。

5.座標をインデックスに変換する関数 coordinate_to_index

coordinate_to_index() {
    declare -A row_map=( ["A"]=0 ["B"]=1 ["C"]=2 ["D"]=3 ["E"]=4 )
    row_letter=${1^^}
    col_number=$2
    row=${row_map[$row_letter]}
    col=$((col_number - 1))
    index=$((row * 5 + col))
    echo $index
}
  • 目的:ユーザーが入力した座標を配列のインデックスに変換します。
  • 解説
    row_map 連想配列で行の文字を数値にマッピングします。
    ・行と列からインデックスを計算します。

6.ゲームの開始と変数の初期化

place_ships
hits=()

echo "バトルシップゲームへようこそ!"

解説

  • place_ships 関数を呼び出して船を配置します。
  • 攻撃結果を記録する hits 配列を初期化します。
  • ゲーム開始のメッセージを表示します。

7.メインゲームループ

while true; do
    display_board
    read -p "砲撃する座標を入力してください(例:A1): " input
    row_input=${input:0:1}
    col_input=${input:1}
    if [[ ! "$row_input" =~ [A-Ea-e] ]] || [[ ! "$col_input" =~ [1-5] ]]; then
        echo "エラー: A1からE5の範囲で入力してください。"
        continue
    fi
    index=$(coordinate_to_index "$row_input" "$col_input")
    if [ "${hits[$index]}" ]; then
        echo "既に砲撃した座標です。別の座標を選んでください。"
        continue
    fi
    if [ "${board[$index]}" != "~" ]; then
        echo "命中!"
        hits[$index]="hit"
        board[$index]="X"
        # 全ての船が撃沈されたかチェック
        remaining_ships=0
        for cell in "${board[@]}"; do
            if [[ "$cell" == "C" || "$cell" == "S" || "$cell" == "D" ]]; then
                remaining_ships=$((remaining_ships + 1))
            fi
        done
        if [ "$remaining_ships" -eq 0 ]; then
            echo "全ての船を撃沈しました!あなたの勝ちです。"
            break
        fi
    else
        echo "外れ!"
        hits[$index]="miss"
    fi
done

解説

  • ユーザー入力の処理
    ・座標を読み取り、行と列に分割します。
    ・正規表現で入力のバリデーションを行います。
  • 攻撃結果の判定
    ・既に攻撃した座標かどうかをチェックします。
    ・命中か外れかを判定し、hits 配列に結果を記録します。
  • 勝利条件のチェック
    ・ボード上に残っている船がないかを確認します。
    ・全ての船が撃沈された場合、ゲームを終了します。

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

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

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

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

スクリプトを実行して、バトルシップゲームを開始します。

user01@ubuntu:~$ ./battleship.sh

実行例

バトルシップゲームへようこそ!
  1 2 3 4 5
A ~ ~ ~ ~ ~
B ~ ~ ~ ~ ~
C ~ ~ ~ ~ ~
D ~ ~ ~ ~ ~
E ~ ~ ~ ~ ~

砲撃する座標を入力してください(例:A1): B2
外れ!

  1 2 3 4 5
A ~ ~ ~ ~ ~
B ~ O ~ ~ ~
C ~ ~ ~ ~ ~
D ~ ~ ~ ~ ~
E ~ ~ ~ ~ ~

砲撃する座標を入力してください(例:A1): D4
命中!

  1 2 3 4 5
A ~ ~ ~ ~ ~
B ~ O ~ ~ ~
C ~ ~ ~ ~ ~
D ~ ~ ~ X ~
E ~ ~ ~ ~ ~

砲撃する座標を入力してください(例:A1): A3
外れ!

(中略)

全ての船を撃沈しました!あなたの勝ちです。

解説

  • ユーザーは砲撃したい座標を入力します。
  • 攻撃結果が「命中」または「外れ」として表示されます。
  • ボードが更新され、命中箇所は X、外れた箇所は O で表示されます。
  • 全ての船を撃沈するとゲームクリアとなります。

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

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

user01@ubuntu:~$ rm battleship.sh

まとめ

  • シェルスクリプトの活用:シェルスクリプトを使って、バトルシップゲームを作成しました。これにより、スクリプトの基本的な構文や制御構造を学ぶことができます。
  • 配列の操作:ボードや攻撃結果を配列で管理し、効率的にデータを扱いました。
  • 関数の定義と利用:再利用可能なコードを関数として定義し、コードの可読性と保守性を向上させました。
  • ランダム性の導入$RANDOM 変数を使用して、船の配置にランダム性を持たせました。
  • 条件分岐とループif 文や while ループを使って、ゲームの進行とロジックを制御しました。
  • 文字列操作:ユーザーの入力を解析し、座標をインデックスに変換するなどの文字列操作を行いました。

 シェルスクリプトは、システム管理以外にも多くの用途で活用できます。今回のバトルシップゲームを基に、ボードサイズの拡大や船の種類・サイズの増加、攻撃回数の制限など、さらなる機能拡張に挑戦してみてください。