シェルスクリプト:オセロゲーム

 オセロゲームは、黒と白の石を使って対戦するボードゲームで、相手の石を挟んで自分の色に変えていく戦略性の高いゲームです。ここでは、シェルスクリプトでオセロゲームを作成し、その実装方法と手順を詳しく解説します。シェルスクリプトを用いてゲームを作成することで、プログラミングの基礎やシェルの機能を深く理解することができます。

オセロゲームの概要

ゲームの目的

  • 自分の色の石をボード上で相手より多く配置することを目指します。

ゲームのルール

  1. ゲームの進行
    ・8×8のボードを使用します。
    ・プレイヤーは黒石(X)、コンピュータは白石(O)を担当します。
    ・最初にボード中央に4つの石が配置されます。
  2. 石の配置
    ・自分の石で相手の石を挟むと、挟まれた相手の石は自分の色に変わります。
    ・石は縦・横・斜めの8方向に置くことができます。
    ・挟める場所がない場合、ターンをパスします。
  3. 勝利条件
    ・ボード上の全てのマスが埋まるか、双方ともに石を置けなくなるとゲーム終了です。
    ・自分の色の石が相手より多ければ勝利となります。

開発のポイント

  • 配列の活用:ボードの状態を二次元配列で管理します。
  • 条件分岐とループ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方向の移動を表します。

方向の定義

dxdy方向
-1-1左上
-10
-11右上
0-1
01
1-1左下
10
11右下
  • ゲームが終了するまで、プレイヤーとコンピュータのターンを繰り返します。
  • 各ターンで有効な手を確認し、プレイヤーは入力に基づいて石を置きます。
  • コンピュータは最初に見つけた有効な手を選びます。

ステップ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を導入したりして、さらなる機能拡張に挑戦してみてください。