シェルスクリプト:オセロゲーム
オセロゲームは、黒と白の石を使って対戦するボードゲームで、相手の石を挟んで自分の色に変えていく戦略性の高いゲームです。ここでは、シェルスクリプトでオセロゲームを作成し、その実装方法と手順を詳しく解説します。シェルスクリプトを用いてゲームを作成することで、プログラミングの基礎やシェルの機能を深く理解することができます。
オセロゲームの概要
ゲームの目的
- 自分の色の石をボード上で相手より多く配置することを目指します。
ゲームのルール
- ゲームの進行
・8×8のボードを使用します。
・プレイヤーは黒石(X
)、コンピュータは白石(O
)を担当します。
・最初にボード中央に4つの石が配置されます。 - 石の配置
・自分の石で相手の石を挟むと、挟まれた相手の石は自分の色に変わります。
・石は縦・横・斜めの8方向に置くことができます。
・挟める場所がない場合、ターンをパスします。 - 勝利条件
・ボード上の全てのマスが埋まるか、双方ともに石を置けなくなるとゲーム終了です。
・自分の色の石が相手より多ければ勝利となります。
開発のポイント
- 配列の活用:ボードの状態を二次元配列で管理します。
- 条件分岐とループ:
if
文やwhile
ループを用いてゲームのロジックを実装します。 - ユーザー入力の処理:
read
コマンドでユーザーからの入力を取得し、バリデーションを行います。 - 関数の定義:再利用可能なコードを関数として整理し、可読性と保守性を向上させます。
othello.sh
の作成から実行までの手順と解説
ステップ1:スクリプトファイルの作成
ターミナルで以下のコマンドを実行し、新しいスクリプトファイルothello.sh
を作成します。
user01@ubuntu:~$ nano othello.sh
ステップ2:スクリプトの内容を記述
エディタが開いたら、以下のコードをコピーして貼り付けます。
#!/bin/bash
# ボードの初期化
initialize_board() {
for ((i=0; i<8; i++)); do
for ((j=0; j<8; j++)); do
board["$i,$j"]="."
done
done
board["3,3"]="O"
board["3,4"]="X"
board["4,3"]="X"
board["4,4"]="O"
}
# ボードの表示
display_board() {
echo " 0 1 2 3 4 5 6 7"
for ((i=0; i<8; i++)); do
echo -n "$i "
for ((j=0; j<8; j++)); do
echo -n "${board["$i,$j"]} "
done
echo
done
}
# 有効な手の確認
valid_moves() {
moves=()
local player=$1
local opponent
if [ "$player" == "X" ]; then
opponent="O"
else
opponent="X"
fi
for ((i=0; i<8; i++)); do
for ((j=0; j<8; j++)); do
if [ "${board["$i,$j"]}" == "." ]; then
for dir in "${directions[@]}"; do
dx=$(echo $dir | cut -d' ' -f1)
dy=$(echo $dir | cut -d' ' -f2)
x=$((i + dx))
y=$((j + dy))
found_opponent=false
while [ $x -ge 0 ] && [ $x -lt 8 ] && [ $y -ge 0 ] && [ $y -lt 8 ]; do
if [ "${board["$x,$y"]}" == "$opponent" ]; then
found_opponent=true
elif [ "${board["$x,$y"]}" == "$player" ]; then
if $found_opponent; then
moves+=("$i,$j")
fi
break
else
break
fi
x=$((x + dx))
y=$((y + dy))
done
done
fi
done
}
}
# 石を置く
place_disk() {
local x=$1
local y=$2
local player=$3
local opponent
if [ "$player" == "X" ]; then
opponent="O"
else
opponent="X"
fi
board["$x,$y"]=$player
for dir in "${directions[@]}"; do
dx=$(echo $dir | cut -d' ' -f1)
dy=$(echo $dir | cut -d' ' -f2)
nx=$((x + dx))
ny=$((y + dy))
to_flip=()
while [ $nx -ge 0 ] && [ $nx -lt 8 ] && [ $ny -ge 0 ] && [ $ny -lt 8 ]; do
if [ "${board["$nx,$ny"]}" == "$opponent" ]; then
to_flip+=("$nx,$ny")
elif [ "${board["$nx,$ny"]}" == "$player" ]; then
for pos in "${to_flip[@]}"; do
board["$pos"]=$player
done
break
else
break
fi
nx=$((nx + dx))
ny=$((ny + dy))
done
done
}
# ゲーム終了の確認
is_game_over() {
valid_moves "X"
x_moves=("${moves[@]}")
valid_moves "O"
o_moves=("${moves[@]}")
if [ ${#x_moves[@]} -eq 0 ] && [ ${#o_moves[@]} -eq 0 ]; then
return 0
else
return 1
fi
}
# 得点の計算
calculate_score() {
x_score=0
o_score=0
for ((i=0; i<8; i++)); do
for ((j=0; j<8; j++)); do
if [ "${board["$i,$j"]}" == "X" ]; then
x_score=$((x_score + 1))
elif [ "${board["$i,$j"]}" == "O" ]; then
o_score=$((o_score + 1))
fi
done
}
}
# メインゲームループ
declare -A board
directions=('-1 -1' '-1 0' '-1 1' '0 -1' '0 1' '1 -1' '1 0' '1 1')
initialize_board
current_player="X" # プレイヤーは "X"
opponent="O" # コンピュータは "O"
while true; do
display_board
calculate_score
echo "あなたの得点:$x_score, コンピュータの得点:$o_score"
valid_moves "$current_player"
if [ ${#moves[@]} -eq 0 ]; then
echo "あなたはパスします。"
else
while true; do
read -p "石を置く場所を入力してください(行 列): " x y
if ! [[ "$x" =~ ^[0-7]$ ]] || ! [[ "$y" =~ ^[0-7]$ ]]; then
echo "無効な入力です。0〜7の数字を入力してください。"
continue
fi
move="$x,$y"
if [[ " ${moves[@]} " =~ " $move " ]]; then
place_disk "$x" "$y" "$current_player"
break
else
echo "その場所には置けません。"
fi
done
fi
# ゲーム終了の確認
is_game_over
if [ $? -eq 0 ]; then
break
fi
# コンピュータのターン
current_player="O"
opponent="X"
valid_moves "$current_player"
if [ ${#moves[@]} -eq 0 ]; then
echo "コンピュータはパスしました。"
else
# 簡単な戦略:最初の有効な手を選ぶ
IFS=',' read -r x y <<< "${moves[0]}"
echo "コンピュータが石を置きました:$x, $y"
place_disk "$x" "$y" "$current_player"
fi
# ゲーム終了の確認
is_game_over
if [ $? -eq 0 ]; then
break
fi
current_player="X"
opponent="O"
done
display_board
calculate_score
echo "最終得点:あなた=$x_score, コンピュータ=$o_score"
if [ "$x_score" -gt "$o_score" ]; then
echo "おめでとうございます!あなたの勝ちです!"
elif [ "$x_score" -lt "$o_score" ]; then
echo "残念!コンピュータの勝ちです。"
else
echo "引き分けです!"
fi
ステップ3:スクリプトの内容の解説
1.シェバン行
#!/bin/bash
- スクリプトがBashシェルで実行されることを指定します。
2.ボードの初期化関数 initialize_board
initialize_board() {
for ((i=0; i<8; i++)); do
for ((j=0; j<8; j++)); do
board["$i,$j"]="."
done
done
board["3,3"]="O"
board["3,4"]="X"
board["4,3"]="X"
board["4,4"]="O"
}
- 目的:8×8のボードを初期化し、中央に初期配置を行います。
- 解説:
・二重ループで全てのマスを"."
(空きマス)で初期化します。
・中央の4つのマスに初期の石を配置します。
3.ボードの表示関数 display_board
display_board() {
echo " 0 1 2 3 4 5 6 7"
for ((i=0; i<8; i++)); do
echo -n "$i "
for ((j=0; j<8; j++)); do
echo -n "${board["$i,$j"]} "
done
echo
done
}
- 目的:現在のボードの状態を表示します。
- 解説:行番号と列番号を表示し、視覚的にボードの状態を確認できます。
4.有効な手の確認関数 valid_moves
valid_moves() {
moves=()
local player=$1
local opponent
if [ "$player" == "X" ]; then
opponent="O"
else
opponent="X"
fi
for ((i=0; i<8; i++)); do
for ((j=0; j<8; j++)); do
if [ "${board["$i,$j"]}" == "." ]; then
for dir in "${directions[@]}"; do
dx=$(echo $dir | cut -d' ' -f1)
dy=$(echo $dir | cut -d' ' -f2)
x=$((i + dx))
y=$((j + dy))
found_opponent=false
while [ $x -ge 0 ] && [ $x -lt 8 ] && [ $y -ge 0 ] && [ $y -lt 8 ]; do
if [ "${board["$x,$y"]}" == "$opponent" ]; then
found_opponent=true
elif [ "${board["$x,$y"]}" == "$player" ]; then
if $found_opponent; then
moves+=("$i,$j")
fi
break
else
break
fi
x=$((x + dx))
y=$((y + dy))
done
done
fi
done
}
}
- 目的:プレイヤーが石を置ける有効な位置を検出します。
- 解説:
・空きマスを検出し、その周囲8方向を調査します。
・相手の石を挟める位置をmoves
配列に追加します。
5.石を置く関数 place_disk
place_disk() {
local x=$1
local y=$2
local player=$3
local opponent
if [ "$player" == "X" ]; then
opponent="O"
else
opponent="X"
fi
board["$x,$y"]=$player
for dir in "${directions[@]}"; do
dx=$(echo $dir | cut -d' ' -f1)
dy=$(echo $dir | cut -d' ' -f2)
nx=$((x + dx))
ny=$((y + dy))
to_flip=()
while [ $nx -ge 0 ] && [ $nx -lt 8 ] && [ $ny -ge 0 ] && [ $ny -lt 8 ]; do
if [ "${board["$nx,$ny"]}" == "$opponent" ]; then
to_flip+=("$nx,$ny")
elif [ "${board["$nx,$ny"]}" == "$player" ]; then
for pos in "${to_flip[@]}"; do
board["$pos"]=$player
done
break
else
break
fi
nx=$((nx + dx))
ny=$((ny + dy))
done
done
}
- 目的:指定した位置に石を置き、挟んだ相手の石を自分の色にひっくり返します。
- 解説:石を置く位置とプレイヤーの色を受け取り、8方向をチェックして相手の石をひっくり返します。
6.ゲーム終了の確認関数 is_game_over
is_game_over() {
valid_moves "X"
x_moves=("${moves[@]}")
valid_moves "O"
o_moves=("${moves[@]}")
if [ ${#x_moves[@]} -eq 0 ] && [ ${#o_moves[@]} -eq 0 ]; then
return 0
else
return 1
fi
}
- 目的:両プレイヤーともに石を置ける場所がないかを確認し、ゲームの終了を判定します。
7.得点の計算関数 calculate_score
calculate_score() {
x_score=0
o_score=0
for ((i=0; i<8; i++)); do
for ((j=0; j<8; j++)); do
if [ "${board["$i,$j"]}" == "X" ]; then
x_score=$((x_score + 1))
elif [ "${board["$i,$j"]}" == "O" ]; then
o_score=$((o_score + 1))
fi
done
}
}
- 目的:現在のボードから各プレイヤーの得点を計算します。
8.メインゲームループ
declare -A board
directions=('-1 -1' '-1 0' '-1 1' '0 -1' '0 1' '1 -1' '1 0' '1 1')
initialize_board
current_player="X" # プレイヤーは "X"
opponent="O" # コンピュータは "O"
while true; do
display_board
calculate_score
echo "あなたの得点:$x_score, コンピュータの得点:$o_score"
valid_moves "$current_player"
if [ ${#moves[@]} -eq 0 ]; then
echo "あなたはパスします。"
else
while true; do
read -p "石を置く場所を入力してください(行 列): " x y
if ! [[ "$x" =~ ^[0-7]$ ]] || ! [[ "$y" =~ ^[0-7]$ ]]; then
echo "無効な入力です。0〜7の数字を入力してください。"
continue
fi
move="$x,$y"
if [[ " ${moves[@]} " =~ " $move " ]]; then
place_disk "$x" "$y" "$current_player"
break
else
echo "その場所には置けません。"
fi
done
fi
# ゲーム終了の確認
is_game_over
if [ $? -eq 0 ]; then
break
fi
# コンピュータのターン
current_player="O"
opponent="X"
valid_moves "$current_player"
if [ ${#moves[@]} -eq 0 ]; then
echo "コンピュータはパスしました。"
else
# 簡単な戦略:最初の有効な手を選ぶ
IFS=',' read -r x y <<< "${moves[0]}"
echo "コンピュータが石を置きました:$x, $y"
place_disk "$x" "$y" "$current_player"
fi
# ゲーム終了の確認
is_game_over
if [ $? -eq 0 ]; then
break
fi
current_player="X"
opponent="O"
done
display_board
calculate_score
echo "最終得点:あなた=$x_score, コンピュータ=$o_score"
if [ "$x_score" -gt "$o_score" ]; then
echo "おめでとうございます!あなたの勝ちです!"
elif [ "$x_score" -lt "$o_score" ]; then
echo "残念!コンピュータの勝ちです。"
else
echo "引き分けです!"
fi
解説
directions
配列は8方向の移動を表します。
方向の定義
dx | dy | 方向 |
---|---|---|
-1 | -1 | 左上 |
-1 | 0 | 上 |
-1 | 1 | 右上 |
0 | -1 | 左 |
0 | 1 | 右 |
1 | -1 | 左下 |
1 | 0 | 下 |
1 | 1 | 右下 |
- ゲームが終了するまで、プレイヤーとコンピュータのターンを繰り返します。
- 各ターンで有効な手を確認し、プレイヤーは入力に基づいて石を置きます。
- コンピュータは最初に見つけた有効な手を選びます。
ステップ4:スクリプトに実行権限を付与
スクリプトを保存してエディタを終了したら、実行権限を付与します。
user01@ubuntu:~$ chmod +x othello.sh
ステップ5:スクリプトの実行
スクリプトを実行して、オセロゲームを開始します。
user01@ubuntu:~$ ./othello.sh
実行例
0 1 2 3 4 5 6 7
0 . . . . . . . .
1 . . . . . . . .
2 . . . . . . . .
3 . . . O X . . .
4 . . . X O . . .
5 . . . . . . . .
6 . . . . . . . .
7 . . . . . . . .
あなたの得点:2, コンピュータの得点:2
石を置く場所を入力してください(行 列): 2 3
その場所には置けません。
石を置く場所を入力してください(行 列): 2 4
その場所には置けません。
石を置く場所を入力してください(行 列): 2 2
その場所には置けません。
石を置く場所を入力してください(行 列): 2 5
(正しい場所を入力すると石が置かれ、ボードが更新されます)
...
解説
- プレイヤーは石を置く位置を「行 列」の形式で入力します。
- 無効な位置を入力すると、エラーメッセージが表示されます。
- コンピュータのターンでは、自動的に石が置かれます。
ここで作成したファイルの削除
ゲームのテストが終了したら、作成したothello.sh
ファイルを削除します。
user01@ubuntu:~$ rm othello.sh
まとめ
- シェルスクリプトの応用:オセロゲームを通じて、シェルスクリプトで複雑なゲームロジックを実装する方法を学びました。
- 配列の操作:二次元配列を使用してボードを管理し、効率的なデータ操作を行いました。
- 条件分岐とループ:
if
文やwhile
ループを駆使してゲームの進行とロジックを制御しました。 - ユーザー入力の処理:
read
コマンドでユーザーの入力を取得し、入力のバリデーションを行いました。 - 関数の活用:関連する処理を関数として整理し、コードの再利用性と可読性を向上させました。
- ゲームロジックの実装:オセロのルールに基づく複雑なロジックをシェルスクリプトで実現しました。
シェルスクリプトはシステム管理だけでなく、さまざまな用途で活用できます。今回のオセロゲームを基に、コンピュータのAIを強化したり、GUIを導入したりして、さらなる機能拡張に挑戦してみてください。