Unityでボーリングゲームを作る #9 スコアを計算するアルゴリズム - Unityの使い方



Unity -> スクリプト -> Unityでボーリングゲームを作る #9 スコアを計算するアルゴリズム

Unityでボーリングゲームを作る #9 スコアを計算するアルゴリズム




ボーリングのスコアを計算してみます。1フレーム2回ずつ投げて、その合計を足して行きますが、スペアを取ると、次の1投分、ストライクを取ると次の2投分のスコアも同じフレームのスコアに加算されます。

つまり、スペアの次に8本倒すと10 + 8 = 18点、ストライクの後にストライク→7本と倒すと 10 + 10 + 7 = 27点が入るそうです。
なので、倒したピンの本数をリストに入れていって、その値をforeach文で順番に取り出してスコアを計算するときに、スペアやストライクが出たら、その先のスコアが存在するかを確認して、存在しなければスコアを計算せずに終了するようなアルゴリズムにしました。
倒したピンの数を入れるリストとは別に、フレームごとのスコアを入れるリストも作ります。

1投目なのか2投目なのかを、倒したピンの数のリストのインデックスが偶数か奇数かで判断するために、ストライクの後には続けて-1を追加しています。

なので、ストライクの次のスコアは、リストの2つ先にあり、それがストライクだった場合、計算するべき2つ目の値は4つ先にあります。{... 10, -1, 10, -1, 6,...

List<int> CalculateScores(List<int> pinsList)
{
    int c = pinsList.Count;
    if (c == 0) new List<int>();

    List<int> scores = new List<int>();
    int i = 0;

    foreach (int pin in pinsList)
    {
        if (pin >= 10 && i % 2 == 0) // ストライクのとき
        {

            if (c > i + 3) // 3つ先の要素があるとき
            {
                if (pinsList[i + 3] == -1) // 次もストライクのとき
                {
                    if (c > i + 4) // 4つ先の要素があるとき
                    {
                        scores.Add(10 + pinsList[i + 2] + pinsList[i + 4]);
                    }
                    else
                    { // 4つ先の要素がないとき
                        return scores;
                    }
                }
                else // 次がストライクでないとき
                {
                    scores.Add(10 + pinsList[i + 2] + pinsList[i + 3]);
                }

            }
            else // 3つ先の要素がないとき
            {

                return scores;
            }              

        }
        else if (pin == -1) // ストライクを取ったフレームの2投目
        {
            // 何もしない
        }
        else
        { // ストライクでないとき

            if (i % 2 == 1) 
            {
                int sum = pinsList[i - 1] + pinsList[i];

                if(sum >= 10) // スペアのとき
                { 
                    if (c > i + 1) // 1つ先の要素があるとき
                    {
                        scores.Add(10 + pinsList[i + 1]);
                    }
                    else // 1つ先の要素がないとき
                    {                           
                        return scores;
                    }
                }
                else // スペアでないとき
                {
                    scores.Add(sum);
                }
            }
                
        }

        i++;
    }

    return scores;
}

はじめ、10本倒れたときはストライクと判断していましたが、1投目にガターで、2投目で10本倒した場合もストライクと判定されてしまうことに気づきました。

こうすると、2投目の10をストライクと判定して、10 - 1 + 8 を計算してしまいます。

条件を変えてスペアと判定されるようにしました。


using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;


public class GameScript3 : MonoBehaviour
{
    public Text text;
    public Text text2;
    public Text text3;
    public Text scoreText;

    GameObject[] pins;
    public float q = 0.01f;

    Animator pinsetterAnimator;
    public Animator barAnimator;
    public GameObject pinsetter;

    bool setpin = false;

    // スコアを入れるリスト
    List<int> playerPins;
    List<int> npcPins;

    public GameObject bowling_pin; // ピンのプレハブ
    public GameObject leadingPin; // 新しく作るピンの先頭の位置を教える空のゲームオブジェクト

    // Start is called before the first frame update
    void Start()
    {
        text.text = "";
        pinsetterAnimator = pinsetter.GetComponent<Animator>();

        playerPins = new List<int>();
        npcPins = new List<int>();
        text2.text = "FRAME 1";
    }

    // Update is called once per frame
    void Update()
    {

        if(Input.GetKeyDown(KeyCode.R))
        {
            SceneManager.LoadScene("Bowling");
        }

    }

    void CountStandingPins()
    {
        pins = GameObject.FindGameObjectsWithTag("Pin"); //「Pin」タグのついたオブジェクトを探して、全て配列pinsにいれる

        setpin = true;
        StartCoroutine("SetPin", pins);        
    }


    IEnumerator SetPin()
    {
    barAnimator.SetTrigger("barTrig"); // バーが降りる
    yield return new WaitForSeconds(2f);

        // 立っているピンの数を数える
        int n = 0;

        foreach (GameObject pin in pins)
        {
            if (pin.transform.rotation.x * pin.transform.rotation.x < q && pin.transform.rotation.z * pin.transform.rotation.z < q)
            {
                n++;

            }
        }

        // text.text = n.ToString();


        // 倒れたピンの数をリストに追加
        if (playerPins.Count % 2 == 0) // 1投目
        {
            playerPins.Add(10 - n);

        }
        else { // 2投目

            playerPins.Add(10 - playerPins[playerPins.Count - 1] - n);

        }


        if (playerPins.Count % 2 == 1 && n == 0) 
        {
            playerPins.Add(-1); // 1投目でストライクのときは2投目は無いので、配列にその分の -1 を入れて要素数を調整する。
        }



        // 倒した本数を表示
        if (playerPins.Count != 0)
        {

            string scoreString2 = " ";

            int n2 = 0;
            foreach (int s in playerPins)
            {

                if (n2 == 0 || n2 % 2 == 1)
                {
                    scoreString2 += s + " ";
                }
                else
                {
                    scoreString2 += " " + s + " ";
                }

                n2++;
            }

            text3.text = scoreString2;
        }
        

        text2.text = "";

        // スコアを計算する
        List<int> playerScore = CalculateScores(playerPins);

        if (playerScore.Count != 0)
        {
            string scoreString = " ";

            foreach (int s in playerScore)
            {
                scoreString += s + "  ";
            }
            scoreText.text = scoreString;

        }

        

        if (playerPins.Count % 2 == 1 && playerPins[playerPins.Count - 1] < 10) // 1投目で、ストライク以外の時
        {
            pinsetterAnimator.SetTrigger("Pinsetter"); // 天井が降りる

            foreach (GameObject pin in pins)
            {
                if (pin.transform.rotation.x * pin.transform.rotation.x < q && pin.transform.rotation.z * pin.transform.rotation.z < q)
                {
                    pin.GetComponent<Rigidbody>().isKinematic = true; // 立っているピンの動きを止める。
                }

            }

            yield return new WaitForSeconds(1.7f);

            //text.text = "";

            foreach (GameObject pin in pins)
            {
                if (pin.transform.rotation.x * pin.transform.rotation.x < q && pin.transform.rotation.z * pin.transform.rotation.z < q)
                {
                    pin.transform.parent = pinsetter.transform; // 立っているピンを天井の子オブジェクトにする。
                }
            }

            pinsetterAnimator.SetTrigger("Pinsetter"); // 天井が上がる
        }
        else {
            yield return new WaitForSeconds(0.5f);
            //text.text = "";
        }


        yield return new WaitForSeconds(1f);
    barAnimator.SetTrigger("barTrig"); // バーがピンを排除

    yield return new WaitForSeconds(0.6f);

        if (playerPins.Count % 2 == 0 || playerPins[playerPins.Count - 1] == 10) // 2投目のときまたは、ストライクかスペアのとき
        {
            // 今あるピンを全て削除
            foreach (GameObject pin in pins)
            {
                Destroy(pin);
            }

            SetNewPins(leadingPin.transform, 0.7f, 4);// 新しいピンを作る

            foreach (GameObject pin in GameObject.FindGameObjectsWithTag("Pin"))
            {
                pin.GetComponent<Rigidbody>().isKinematic = true; // 物理で動かないようにする
                pin.transform.parent = pinsetter.transform; // ピンを天井の子オブジェクトにする
            }
        }

    //yield return new WaitForSeconds(1f);  

    pinsetterAnimator.SetTrigger("Pinsetter"); // 天井が降りる

        
        yield return new WaitForSeconds(1.5f);

    foreach (GameObject pin in GameObject.FindGameObjectsWithTag("Pin"))
    {
        if (pin.transform.rotation.x * pin.transform.rotation.x < q && pin.transform.rotation.z * pin.transform.rotation.z < q)
        {
            pin.transform.parent = null; // 立っているピンと天井の親子関係を解く
            pin.GetComponent<Rigidbody>().isKinematic = false;
        }
    }
    barAnimator.SetTrigger("barTrig"); // バーが上がる

        


        pinsetterAnimator.SetTrigger("Pinsetter"); // 天井が上がる
        yield return new WaitForSeconds(0.7f);



        yield return new WaitForSeconds(0.4f);
        //yield return new WaitForSeconds(1.1f);
        setpin = false;
    
    //yield return new WaitForSeconds(0.5f);
    text2.text = "FRAME " + (playerPins.Count / 2 + 1);
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.tag == "Ball" && !setpin)
        {
            
            CountStandingPins();
        }

    }

    /*
    void GenerateNewPins()
    {
        Vector3[] newPinsPos = {
        new Vector3(-42.29f, 1.318f, 0.0493f),new Vector3(-42.85f, 1.318f, 0.42f),new Vector3(-42.85f, 1.318f, -0.35f),
        new Vector3(-43.34f, 1.318f, 0.79f),new Vector3(-43.34f, 1.318f, 0.02f),new Vector3(-43.34f, 1.318f, -0.769f),
        new Vector3(-43.86f, 1.318f, 1.178f),new Vector3(-43.86f, 1.318f, 0.408f),new Vector3(-43.86f, 1.318f, -0.39f),
        new Vector3(-43.86f, 1.318f, -1.16f),};

        for(int p = 0; p < 10; p++)
        {
            Instantiate(bowling_pin, newPinsPos[p],Quaternion.Euler(0f, 0f, 0f));
        }

    }
    */

    void SetNewPins(Transform t, float interval, int line_total)
    {
        int line = 0; // 行
        int column = 1; // 列

        for (int o = 0; o < line_total; o++)
        {

            for (int p = 0; p < column; p++)
            {
                Vector3 pos = t.position;

                // 先頭のピンを基準に位置を変える
                pos -= t.forward * (interval * line); // 置く列に合わせて後ろに下げる
                pos += t.right * (interval * p - interval / 2 * line); // 左方向に動かす

                Instantiate(bowling_pin, pos, Quaternion.Euler(0f, 0f, 0f));
            }

            line++;
            column++;
        }

    }

    List<int> CalculateScores(List<int> pinsList)
    {
        int c = pinsList.Count;
        if (c == 0) new List<int>();

        List<int> scores = new List<int>();
        int i = 0;

        foreach (int pin in pinsList)
        {
            if (pin >= 10 && i % 2 == 0) // ストライクのとき
            {

                if (c > i + 3) // 3つ先の要素があるとき
                {
                    if (pinsList[i + 3] == -1) // 次もストライクのとき
                    {
                        if (c > i + 4) // 4つ先の要素があるとき
                        {
                            scores.Add(10 + pinsList[i + 2] + pinsList[i + 4]);
                        }
                        else
                        { // 4つ先の要素がないとき
                            return scores;
                        }
                    }
                    else // 次がストライクでないとき
                    {
                        scores.Add(10 + pinsList[i + 2] + pinsList[i + 3]);
                    }

                }
                else // 3つ先の要素がないとき
                {

                    return scores;
                }              

            }
            else if (pin == -1) // ストライクを取ったフレームの2投目
            {
                // 何もしない
            }
            else
            { // ストライクでないとき

                if (i % 2 == 1) 
                {
                    int sum = pinsList[i - 1] + pinsList[i];

                    if(sum >= 10) // スペアのとき
                    { 
                        if (c > i + 1) // 1つ先の要素があるとき
                        {
                            scores.Add(10 + pinsList[i + 1]);
                        }
                        else // 1つ先の要素がないとき
                        {                           
                            return scores;
                        }
                    }
                    else // スペアでないとき
                    {
                        scores.Add(sum);
                    }
                }
                
            }

            i++;
        }

        return scores;
    }

}

トップページへ


tags: ボーリング, ボウリング, スコア, 計算, アルゴリズム