シェルスクリプト:ブラックジャックゲーム

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

ブラックジャックゲームの概要

ゲームの目的

  • プレイヤーとディーラーがカードの合計点数を競い、21に近い方が勝ちます。ただし、21を超えるとバスト(負け)になります。

ゲームのルール

  • カードの値
    ・数字カード(2〜10):そのままの数値。
    ・絵札(J、Q、K):10点。
    ・エース(A):1点または11点(手札の合計に応じて有利な方を選択)。
  • ゲームの進行
    ・プレイヤーとディーラーはそれぞれ2枚のカードを引きます。
    ・プレイヤーはカードを追加で引く(ヒット)か、引かない(スタンド)かを選択できます。
    ・ディーラーは手札の合計が17以上になるまでカードを引きます。
  • 勝敗の判定
    ・手札の合計が21を超えた場合、バストとなり負け。
    ・プレイヤーとディーラーの手札の合計を比較し、21に近い方が勝ち。
    ・合計が同じ場合は引き分け。

カードの例

カード
2〜102〜10
J(ジャック)10
Q(クイーン)10
K(キング)10
A(エース)1または11

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

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

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

user01@ubuntu:~$ nano blackjack.sh

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

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

#!/bin/bash

# デッキの作成
create_deck() {
    suits=("Hearts" "Diamonds" "Clubs" "Spades")
    ranks=("2" "3" "4" "5" "6" "7" "8" "9" "10" "J" "Q" "K" "A")
    deck=()
    for suit in "${suits[@]}"; do
        for rank in "${ranks[@]}"; do
            deck+=("$rank of $suit")
        done
    done
}

# カードを引く関数
draw_card() {
    index=$((RANDOM % ${#deck[@]}))
    card="${deck[$index]}"
    deck=("${deck[@]:0:$index}" "${deck[@]:$((index + 1))}")
    echo "$card"
}

# カードの値を取得する関数
get_card_value() {
    rank=$(echo "$1" | awk '{print $1}')
    case "$rank" in
        "2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"10")
            echo "$rank"
            ;;
        "J"|"Q"|"K")
            echo "10"
            ;;
        "A")
            echo "11"
            ;;
    esac
}

# 手札の合計を計算する関数
calculate_total() {
    total=0
    aces=0
    hand=("$@")
    for card in "${hand[@]}"; do
        value=$(get_card_value "$card")
        total=$((total + value))
        if [ "$value" -eq 11 ]; then
            aces=$((aces + 1))
        fi
    done
    while [ $total -gt 21 ] && [ $aces -gt 0 ]; do
        total=$((total - 10))
        aces=$((aces - 1))
    done
    echo "$total"
}

# ゲームの開始
create_deck

player_hand=()
dealer_hand=()

# プレイヤーとディーラーが2枚のカードを引く
player_hand+=("$(draw_card)")
dealer_hand+=("$(draw_card)")
player_hand+=("$(draw_card)")
dealer_hand+=("$(draw_card)")

echo "ブラックジャックへようこそ!"

# プレイヤーのターン
while true; do
    echo
    echo "あなたの手札: ${player_hand[@]}"
    player_total=$(calculate_total "${player_hand[@]}")
    echo "あなたの合計: $player_total"

    if [ "$player_total" -gt 21 ]; then
        echo "バストしました!あなたの負けです。"
        exit
    fi

    read -p "カードを引きますか?(Y/N): " choice
    if [[ "$choice" =~ ^[Yy]$ ]]; then
        player_hand+=("$(draw_card)")
    else
        break
    fi
done

# ディーラーのターン
echo
echo "ディーラーの手札: ${dealer_hand[@]}"
dealer_total=$(calculate_total "${dealer_hand[@]}")
echo "ディーラーの合計: $dealer_total"

while [ "$dealer_total" -lt 17 ]; do
    echo "ディーラーがカードを引きます。"
    dealer_hand+=("$(draw_card)")
    echo "ディーラーの手札: ${dealer_hand[@]}"
    dealer_total=$(calculate_total "${dealer_hand[@]}")
    echo "ディーラーの合計: $dealer_total"
done

# 勝敗の判定
echo
if [ "$dealer_total" -gt 21 ]; then
    echo "ディーラーがバストしました!あなたの勝ちです。"
elif [ "$player_total" -gt "$dealer_total" ]; then
    echo "あなたの勝ちです!"
elif [ "$player_total" -lt "$dealer_total" ]; then
    echo "あなたの負けです。"
else
    echo "引き分けです。"
fi

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

1.シェバン行

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

2.デッキの作成関数 create_deck

create_deck() {
    suits=("Hearts" "Diamonds" "Clubs" "Spades")
    ranks=("2" "3" "4" "5" "6" "7" "8" "9" "10" "J" "Q" "K" "A")
    deck=()
    for suit in "${suits[@]}"; do
        for rank in "${ranks[@]}"; do
            deck+=("$rank of $suit")
        done
    done
}
  • 目的:トランプの全52枚のデッキを作成します。
  • 解説
    suits:カードのスート(柄)を配列に格納。
    ranks:カードのランク(数字や絵札)を配列に格納。
    ・ネストされた for ループで全ての組み合わせを deck 配列に追加。

3.カードを引く関数 draw_card

draw_card() {
    index=$((RANDOM % ${#deck[@]}))
    card="${deck[$index]}"
    deck=("${deck[@]:0:$index}" "${deck[@]:$((index + 1))}")
    echo "$card"
}
  • 目的:デッキからランダムにカードを引き、そのカードを返します。
  • 解説
    $RANDOM を使用してデッキのインデックス範囲内でランダムな数を生成。
    ・選択したカードを deck から削除して重複を防止。
    ・引いたカードを返す。

4.カードの値を取得する関数 get_card_value

get_card_value() {
    rank=$(echo "$1" | awk '{print $1}')
    case "$rank" in
        "2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"10")
            echo "$rank"
            ;;
        "J"|"Q"|"K")
            echo "10"
            ;;
        "A")
            echo "11"
            ;;
    esac
}
  • 目的:カードのランクからその値を取得します。
  • 解説
    awk を使用してカード名からランク部分を抽出。
    case 文でランクに応じた値を返す。

5.手札の合計を計算する関数 calculate_total

calculate_total() {
    total=0
    aces=0
    hand=("$@")
    for card in "${hand[@]}"; do
        value=$(get_card_value "$card")
        total=$((total + value))
        if [ "$value" -eq 11 ]; then
            aces=$((aces + 1))
        fi
    done
    while [ $total -gt 21 ] && [ $aces -gt 0 ]; do
        total=$((total - 10))
        aces=$((aces - 1))
    done
    echo "$total"
}
  • 目的:手札の合計点数を計算し、必要に応じてエース(A)の値を11から1に調整します。
  • 解説
    ・手札の各カードの値を合計。
    ・エースの数をカウント。
    ・合計が21を超え、エースがある場合、合計から10を引きエースの値を1に変更。

6.ゲームの開始と初期設定

create_deck

player_hand=()
dealer_hand=()

# プレイヤーとディーラーが2枚のカードを引く
player_hand+=("$(draw_card)")
dealer_hand+=("$(draw_card)")
player_hand+=("$(draw_card)")
dealer_hand+=("$(draw_card)")

echo "ブラックジャックへようこそ!"

解説

  • デッキを作成。
  • プレイヤーとディーラーの手札を初期化。
  • それぞれ2枚のカードを引く。
  • ゲーム開始のメッセージを表示。

7.プレイヤーのターン

while true; do
    echo
    echo "あなたの手札: ${player_hand[@]}"
    player_total=$(calculate_total "${player_hand[@]}")
    echo "あなたの合計: $player_total"

    if [ "$player_total" -gt 21 ]; then
        echo "バストしました!あなたの負けです。"
        exit
    fi

    read -p "カードを引きますか?(Y/N): " choice
    if [[ "$choice" =~ ^[Yy]$ ]]; then
        player_hand+=("$(draw_card)")
    else
        break
    fi
done

解説

  • 手札と合計を表示。
  • 合計が21を超えた場合、バストとしてゲーム終了。
  • ユーザーにカードを引くかどうかを尋ね、引く場合はカードを追加。

8.ディーラーのターン

echo
echo "ディーラーの手札: ${dealer_hand[@]}"
dealer_total=$(calculate_total "${dealer_hand[@]}")
echo "ディーラーの合計: $dealer_total"

while [ "$dealer_total" -lt 17 ]; do
    echo "ディーラーがカードを引きます。"
    dealer_hand+=("$(draw_card)")
    echo "ディーラーの手札: ${dealer_hand[@]}"
    dealer_total=$(calculate_total "${dealer_hand[@]}")
    echo "ディーラーの合計: $dealer_total"
done

解説

  • ディーラーの手札と合計を表示。
  • 合計が17以上になるまでカードを引く。

9.勝敗の判定

echo
if [ "$dealer_total" -gt 21 ]; then
    echo "ディーラーがバストしました!あなたの勝ちです。"
elif [ "$player_total" -gt "$dealer_total" ]; then
    echo "あなたの勝ちです!"
elif [ "$player_total" -lt "$dealer_total" ]; then
    echo "あなたの負けです。"
else
    echo "引き分けです。"
fi

解説

  • ディーラーがバストした場合、プレイヤーの勝ち。
  • プレイヤーとディーラーの合計を比較し、勝敗を決定。

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

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

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

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

スクリプトを実行して、ブラックジャックゲームを開始します。

user01@ubuntu:~$ ./blackjack.sh

実行例

ブラックジャックへようこそ!

あなたの手札: 7 of Hearts 3 of Clubs
あなたの合計: 10
カードを引きますか?(Y/N): y

あなたの手札: 7 of Hearts 3 of Clubs K of Diamonds
あなたの合計: 20
カードを引きますか?(Y/N): n

ディーラーの手札: 5 of Spades 9 of Hearts
ディーラーの合計: 14
ディーラーがカードを引きます。
ディーラーの手札: 5 of Spades 9 of Hearts 8 of Clubs
ディーラーの合計: 22

ディーラーがバストしました!あなたの勝ちです。

解説

  • プレイヤーのターン
    ・手札は「7 of Hearts」と「3 of Clubs」で合計10点。
    ・カードを引いて「K of Diamonds」を得て、合計20点。
    ・さらに引くか尋ねられ、引かないことを選択。
  • ディーラーのターン
    ・手札は「5 of Spades」と「9 of Hearts」で合計14点。
    ・合計が17未満のため、カードを引く。
    ・「8 of Clubs」を引いて合計22点となり、バスト。
  • 結果
    ・ディーラーがバストしたため、プレイヤーの勝ち。

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

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

user01@ubuntu:~$ rm blackjack.sh

まとめ

  • シェルスクリプトの応用:ブラックジャックゲームを通じて、シェルスクリプトでのゲーム作成方法を学びました。
  • 配列の操作:デッキや手札を配列で管理し、カードの操作を効率的に行いました。
  • 関数の定義と利用:再利用可能なコードを関数として定義し、コードの可読性と保守性を向上させました。
  • ランダム性の導入$RANDOM 変数を使用して、ゲームに必要なランダム性を実現しました。
  • 条件分岐とループif 文や while ループを使って、ゲームの進行とロジックを制御しました。
  • 文字列操作:カード名の解析や表示など、文字列操作を行いました。

 シェルスクリプトは、システム管理以外にも多くの用途で活用できます。今回のブラックジャックゲームを基に、ベット機能の追加や複数ラウンドの実装、他のプレイヤーとの対戦など、さらなる機能拡張に挑戦してみてください。