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

 ポーカーは、戦略と駆け引きを楽しむことができるトランプゲームで、世界中で親しまれています。ここでは、シェルスクリプトを使ってシンプルなポーカーゲームを作成し、その手順と詳細な解説を行います。シェルスクリプトでゲームを作成することで、プログラミングの基礎やシェルの機能を深く理解することができます。

ポーカーゲームの概要

ゲームの目的

  • 手札を交換して、より強い役を作り、ディーラーに勝つことを目指します。

ゲームのルール

  1. カードの配布
    ・プレイヤーとディーラーにそれぞれ5枚のカードが配られます。
  2. カードの交換
    ・プレイヤーは手札から最大3枚までのカードを交換できます。
    ・ディーラーも簡易的な戦略でカードを交換します。
  3. 役の判定
    ・交換後、プレイヤーとディーラーの手札の役を判定します。
  4. 勝敗の決定
    ・手札の役の強さを比較し、強い方が勝利となります。

役の一覧と強さ

ポーカーの役は、以下のように強さが決まっています。

役の強さ役の名前説明
10ロイヤルストレートフラッシュ同じスートで10からAまでの連番
9ストレートフラッシュ同じスートでの連番
8フォーカード同じ数字のカードが4枚
7フルハウススリーカードとワンペアの組み合わせ
6フラッシュ同じスートのカードが5枚
5ストレート異なるスートでの連番
4スリーカード同じ数字のカードが3枚
3ツーペア2つの異なるペアがある
2ワンペア同じ数字のカードが2枚
1ハイカード上記の役が何もない場合

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

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

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

user01@ubuntu:~$ nano poker.sh

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

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

#!/bin/bash

# カードのデッキを作成
create_deck() {
    suits=("♠" "♥" "♦" "♣")
    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$suit")
        done
    done
}

# デッキをシャッフル
shuffle_deck() {
    for ((i=${#deck[@]} - 1; i > 0; i--)); do
        j=$((RANDOM % (i + 1)))
        temp=${deck[i]}
        deck[i]=${deck[j]}
        deck[j]=$temp
    done
}

# カードを配る
deal_cards() {
    player_hand=("${deck[@]:0:5}")
    dealer_hand=("${deck[@]:5:5}")
    deck=("${deck[@]:10}")
}

# 手札を表示
show_hand() {
    local hand=("$@")
    for i in "${!hand[@]}"; do
        echo -n "[$i]: ${hand[i]}  "
    done
    echo
}

# 役を判定
evaluate_hand() {
    local hand=("$@")
    local ranks=()
    local suits=()
    declare -A rank_count
    declare -A suit_count

    for card in "${hand[@]}"; do
        rank=${card%?}
        suit=${card: -1}
        ranks+=("$rank")
        suits+=("$suit")
    done

    for r in "${ranks[@]}"; do
        ((rank_count[$r]++))
    done

    for s in "${suits[@]}"; do
        ((suit_count[$s]++))
    done

    # フラッシュの判定
    flush=false
    for count in "${suit_count[@]}"; do
        if [ "$count" -eq 5 ]; then
            flush=true
        fi
    done

    # ストレートの判定
    rank_values=("2" "3" "4" "5" "6" "7" "8" "9" "10" "J" "Q" "K" "A")
    rank_indices=()
    for r in "${ranks[@]}"; do
        for i in "${!rank_values[@]}"; do
            if [ "$r" == "${rank_values[i]}" ]; then
                rank_indices+=("$i")
            fi
        done
    done
    IFS=$'\n' sorted_indices=($(sort -n <<<"${rank_indices[*]}"))
    unset IFS
    straight=true
    for ((i=1; i<${#sorted_indices[@]}; i++)); do
        if [ $((${sorted_indices[i]} - ${sorted_indices[i-1]})) -ne 1 ]; then
            straight=false
            break
        fi
    done

    # その他の役の判定
    counts=($(printf "%s\n" "${rank_count[@]}" | sort -nr))
    if $flush && $straight; then
        echo "ストレートフラッシュ"
    elif [ "${counts[0]}" -eq 4 ]; then
        echo "フォーカード"
    elif [ "${counts[0]}" -eq 3 ] && [ "${counts[1]}" -eq 2 ]; then
        echo "フルハウス"
    elif $flush; then
        echo "フラッシュ"
    elif $straight; then
        echo "ストレート"
    elif [ "${counts[0]}" -eq 3 ]; then
        echo "スリーカード"
    elif [ "${counts[0]}" -eq 2 ] && [ "${counts[1]}" -eq 2 ]; then
        echo "ツーペア"
    elif [ "${counts[0]}" -eq 2 ]; then
        echo "ワンペア"
    else
        echo "ハイカード"
    fi
}

# カードを交換
exchange_cards() {
    local hand=("$@")
    local indices=()
    read -p "交換したいカードの番号をスペースで区切って入力(最大3枚、例: 0 2 4、交換しない場合はn): " input
    if [ "$input" != "n" ]; then
        indices=($input)
        if [ "${#indices[@]}" -le 3 ]; then
            for index in "${indices[@]}"; do
                hand[$index]=${deck[0]}
                deck=("${deck[@]:1}")
            done
        else
            echo "交換は最大3枚までです。"
            exchange_cards "${hand[@]}"
        fi
    fi
    echo "${hand[@]}"
}

# ゲーム開始
create_deck
shuffle_deck
deal_cards

echo "=== ポーカーゲームへようこそ! ==="

# プレイヤーの手札
echo "あなたの手札:"
show_hand "${player_hand[@]}"
player_hand=($(exchange_cards "${player_hand[@]}"))
echo "最終的なあなたの手札:"
show_hand "${player_hand[@]}"

# ディーラーの手札(簡易的な戦略で交換)
# ここでは役がワンペア以下の場合にランダムで1~3枚交換
dealer_role=$(evaluate_hand "${dealer_hand[@]}")
case $dealer_role in
    "ハイカード"|"ワンペア")
        num_exchange=$(( (RANDOM % 3) + 1 ))
        for ((i=0; i<num_exchange; i++)); do
            index=$((RANDOM % 5))
            dealer_hand[$index]=${deck[0]}
            deck=("${deck[@]:1}")
        done
        ;;
esac

# 手札の役を表示
echo "ディーラーの手札:"
show_hand "${dealer_hand[@]}"
echo "あなたの役:$(evaluate_hand "${player_hand[@]}")"
echo "ディーラーの役:$(evaluate_hand "${dealer_hand[@]}")"

# 勝敗の判定(簡易的に役の強さを比較)
hand_rank() {
    case $1 in
        "ロイヤルストレートフラッシュ") echo 10 ;;
        "ストレートフラッシュ") echo 9 ;;
        "フォーカード") echo 8 ;;
        "フルハウス") echo 7 ;;
        "フラッシュ") echo 6 ;;
        "ストレート") echo 5 ;;
        "スリーカード") echo 4 ;;
        "ツーペア") echo 3 ;;
        "ワンペア") echo 2 ;;
        "ハイカード") echo 1 ;;
    esac
}

player_rank=$(hand_rank "$(evaluate_hand "${player_hand[@]}")")
dealer_rank=$(hand_rank "$(evaluate_hand "${dealer_hand[@]}")")

if [ $player_rank -gt $dealer_rank ]; then
    echo "おめでとうございます!あなたの勝ちです!"
elif [ $player_rank -lt $dealer_rank ]; then
    echo "残念!ディーラーの勝ちです。"
else
    echo "引き分けです。"
fi

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

1.シェバン行

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

2.デッキの作成関数 create_deck

create_deck() {
    suits=("♠" "♥" "♦" "♣")
    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$suit")
        done
    done
}
  • 目的:トランプのデッキ(52枚)を作成します。
  • 解説
    suits:カードのスート(♠, ♥, ♦, ♣)を表す配列。
    ranks:カードのランク(2~A)を表す配列。
    ・二重のforループを使って、全ての組み合わせのカードをdeck配列に追加します。

3.デッキをシャッフルする関数 shuffle_deck

shuffle_deck() {
    for ((i=${#deck[@]} - 1; i > 0; i--)); do
        j=$((RANDOM % (i + 1)))
        temp=${deck[i]}
        deck[i]=${deck[j]}
        deck[j]=$temp
    done
}
  • 目的:デッキをランダムに並び替えます(Fisher-Yatesアルゴリズムを使用)。
  • 解説:配列の要素を後ろから順に、ランダムな位置の要素と交換します。

4.カードを配る関数 deal_cards

deal_cards() {
    player_hand=("${deck[@]:0:5}")
    dealer_hand=("${deck[@]:5:5}")
    deck=("${deck[@]:10}")
}
  • 目的:プレイヤーとディーラーにそれぞれ5枚のカードを配ります。
  • 解説
    player_hand:デッキの最初の5枚をプレイヤーの手札に。
    dealer_hand:次の5枚をディーラーの手札に。
    ・残りのデッキを更新します。

5.手札を表示する関数 show_hand

show_hand() {
    local hand=("$@")
    for i in "${!hand[@]}"; do
        echo -n "[$i]: ${hand[i]}  "
    done
    echo
}
  • 目的:手札を見やすく表示します。
  • 解説:各カードにインデックスを付けて表示し、交換時に参照しやすくしています。

6.役を判定する関数 evaluate_hand

evaluate_hand() {
    # 手札から役を判定し、役名を返します。
}
  • 目的:手札の役を判定します。
  • 解説
    rankssuitsに手札のランクとスートを格納します。
    rank_countsuit_countで各ランクとスートの出現回数を数えます。
    ・フラッシュとストレートの判定を行い、その他の役も判定します。

役の判定の詳細

  • フラッシュの判定
    全てのカードが同じスートであれば、flushtrueにします。
  • ストレートの判定
    ランクを数値化し、ソートして連続しているかを確認します。
  • その他の役の判定
    rank_countの値を降順にソートし、組み合わせによって役を判定します。

7.カードを交換する関数 exchange_cards

exchange_cards() {
    # プレイヤーが交換したいカードを選択し、新しいカードと交換します。
}
  • 目的:プレイヤーが交換したいカードを選択し、デッキから新しいカードを配ります。
  • 解説
    ・ユーザーから交換したいカードのインデックスを入力してもらいます。
    ・最大3枚まで交換可能で、デッキから新しいカードを配ります。

8.ゲームの進行

# ゲーム開始
create_deck
shuffle_deck
deal_cards

echo "=== ポーカーゲームへようこそ! ==="

# プレイヤーの手札
echo "あなたの手札:"
show_hand "${player_hand[@]}"
player_hand=($(exchange_cards "${player_hand[@]}"))
echo "最終的なあなたの手札:"
show_hand "${player_hand[@]}"

解説

  • デッキの作成、シャッフル、カードの配布を行います。
  • プレイヤーの手札を表示し、カードの交換を行います。

9.ディーラーの手札の交換

# ディーラーの手札(簡易的な戦略で交換)
# ここでは役がワンペア以下の場合にランダムで1~3枚交換
dealer_role=$(evaluate_hand "${dealer_hand[@]}")
case $dealer_role in
    "ハイカード"|"ワンペア")
        num_exchange=$(( (RANDOM % 3) + 1 ))
        for ((i=0; i<num_exchange; i++)); do
            index=$((RANDOM % 5))
            dealer_hand[$index]=${deck[0]}
            deck=("${deck[@]:1}")
        done
        ;;
esac
  • 目的:ディーラーが手札を交換します。
  • 解説:ディーラーの役が「ハイカード」または「ワンペア」の場合、1~3枚のカードをランダムに交換します。

10.手札の役を表示

# 手札の役を表示
echo "ディーラーの手札:"
show_hand "${dealer_hand[@]}"
echo "あなたの役:$(evaluate_hand "${player_hand[@]}")"
echo "ディーラーの役:$(evaluate_hand "${dealer_hand[@]}")"
  • 目的:最終的な手札と役を表示します。

11.勝敗の判定

# 勝敗の判定(簡易的に役の強さを比較)
hand_rank() {
    # 各役に数値を割り当てて、役の強さを比較します。
}

player_rank=$(hand_rank "$(evaluate_hand "${player_hand[@]}")")
dealer_rank=$(hand_rank "$(evaluate_hand "${dealer_hand[@]}")")

if [ $player_rank -gt $dealer_rank ]; then
    echo "おめでとうございます!あなたの勝ちです!"
elif [ $player_rank -lt $dealer_rank ]; then
    echo "残念!ディーラーの勝ちです。"
else
    echo "引き分けです。"
fi
  • 目的:プレイヤーとディーラーの役を数値化し、勝敗を決定します。
  • 解説hand_rank関数で各役に対応する数値を返し、それを比較します。

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

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

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

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

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

user01@ubuntu:~$ ./poker.sh

実行例

=== ポーカーゲームへようこそ! ===
あなたの手札:
[0]: 7♠  [1]: K♥  [2]: 2♦  [3]: 7♣  [4]: 9♥  
交換したいカードの番号をスペースで区切って入力(最大3枚、例: 0 2 4、交換しない場合はn): 1 2
最終的なあなたの手札:
[0]: 7♠  [1]: A♠  [2]: 10♦  [3]: 7♣  [4]: 9♥  
ディーラーの手札:
[0]: 5♠  [1]: 5♦  [2]: J♣  [3]: Q♠  [4]: 5♥  
あなたの役:ワンペア
ディーラーの役:スリーカード
残念!ディーラーの勝ちです。

解説

  • カードの交換:プレイヤーは手札の中からカードを選び、デッキから新しいカードと交換します。
  • 役の表示:最終的な手札の役が表示されます。
  • 勝敗の決定:役の強さを比較し、勝者が決定されます。

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

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

user01@ubuntu:~$ rm poker.sh

まとめ

  • シェルスクリプトの応用:ポーカーゲームを通じて、シェルスクリプトでのゲーム開発方法を学びました。
  • 配列の操作:カードのデッキや手札を配列で管理し、シャッフルや交換を実装しました。
  • 関数の活用:機能ごとに関数を定義し、コードの再利用性と可読性を向上させました。
  • 条件分岐とループif文やforループを使って、ゲームのロジックを制御しました。
  • ユーザー入力の処理readコマンドでユーザーからの入力を取得し、入力のバリデーションを行いました。
  • 役の判定ロジック:ポーカーの役を判定するアルゴリズムを実装し、ゲーム性を高めました。

 シェルスクリプトはシステム管理だけでなく、さまざまな用途で活用できます。今回のポーカーゲームを基に、ディーラーの戦略を高度化したり、役の判定をより正確にしたり、マルチプレイヤー機能を追加したりして、さらなる機能拡張に挑戦してみてください。