シェルスクリプト:ポーカーゲーム
ポーカーは、戦略と駆け引きを楽しむことができるトランプゲームで、世界中で親しまれています。ここでは、シェルスクリプトを使ってシンプルなポーカーゲームを作成し、その手順と詳細な解説を行います。シェルスクリプトでゲームを作成することで、プログラミングの基礎やシェルの機能を深く理解することができます。
ポーカーゲームの概要
ゲームの目的
- 手札を交換して、より強い役を作り、ディーラーに勝つことを目指します。
ゲームのルール
- カードの配布
・プレイヤーとディーラーにそれぞれ5枚のカードが配られます。 - カードの交換
・プレイヤーは手札から最大3枚までのカードを交換できます。
・ディーラーも簡易的な戦略でカードを交換します。 - 役の判定
・交換後、プレイヤーとディーラーの手札の役を判定します。 - 勝敗の決定
・手札の役の強さを比較し、強い方が勝利となります。
役の一覧と強さ
ポーカーの役は、以下のように強さが決まっています。
役の強さ | 役の名前 | 説明 |
---|---|---|
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() {
# 手札から役を判定し、役名を返します。
}
- 目的:手札の役を判定します。
- 解説:
・ranks
とsuits
に手札のランクとスートを格納します。
・rank_count
とsuit_count
で各ランクとスートの出現回数を数えます。
・フラッシュとストレートの判定を行い、その他の役も判定します。
役の判定の詳細
- フラッシュの判定
全てのカードが同じスートであれば、flush
をtrue
にします。 - ストレートの判定
ランクを数値化し、ソートして連続しているかを確認します。 - その他の役の判定
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
コマンドでユーザーからの入力を取得し、入力のバリデーションを行いました。 - 役の判定ロジック:ポーカーの役を判定するアルゴリズムを実装し、ゲーム性を高めました。
シェルスクリプトはシステム管理だけでなく、さまざまな用途で活用できます。今回のポーカーゲームを基に、ディーラーの戦略を高度化したり、役の判定をより正確にしたり、マルチプレイヤー機能を追加したりして、さらなる機能拡張に挑戦してみてください。