新Swiftで行こう…第62回「トランプ4解説」 田部井保

目次 通常版

 乱数を発生させる時に、何度も繰り返さなくてはいけないのを止める方法の一例です。

//
//  ViewController.swift
//  Card
//
//  Created by 保 Tabei on 2024/09/25.
//

import UIKit

class ViewController: UIViewController {
    ///カード表示用ラベル
    @IBOutlet weak var lblCard: UILabel!
    ///残り枚数表示用ラベル
    @IBOutlet weak var lblCount: UILabel!

    ///♣️の分、ラベルを定義
    @IBOutlet weak var lblKA: UILabel!
    @IBOutlet weak var lblK2: UILabel!
    @IBOutlet weak var lblK3: UILabel!
    @IBOutlet weak var lblK4: UILabel!
    @IBOutlet weak var lblK5: UILabel!
    @IBOutlet weak var lblK6: UILabel!
    @IBOutlet weak var lblK7: UILabel!
    @IBOutlet weak var lblK8: UILabel!
    @IBOutlet weak var lblK9: UILabel!
    @IBOutlet weak var lblKT: UILabel!
    @IBOutlet weak var lblKJ: UILabel!
    @IBOutlet weak var lblKQ: UILabel!
    @IBOutlet weak var lblKK: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    ///マーク保持配列
    let mark: [String] = ["♣️","♦️","❤️","♠️"]
    ///ナンバー保持配列
    let number: [String] = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]

    ///カード枚数保持定数、今回は♣️限定
    enum EnumCard {
        static let Count = 13
    }
    
    ///既に出ているかチェックする、出ていたらtrue
    ///出ていなかったらfalse、とりあえず
    ///EnumCard.Count枚分falseで埋める
    var check = [Bool](repeating: false, count: EnumCard.Count)

    ///残り枚数を保持する変数
    var count = EnumCard.Count

    ///ボタン押下時処理
    @IBAction func btnGoTouch(_ sender: Any) {
        //残り枚数0なら初期化
        if count == 0 {
            //check配列を全部falseに
            for i in 0 ..< EnumCard.Count
            {
                check[i] = false
            }
            
            //♣️ラベルを初期化
            lblKA.text = ""
            lblK2.text = ""
            lblK3.text = ""
            lblK4.text = ""
            lblK5.text = ""
            lblK6.text = ""
            lblK7.text = ""
            lblK8.text = ""
            lblK9.text = ""
            lblKT.text = ""
            lblKJ.text = ""
            lblKQ.text = ""
            lblKK.text = ""

            //アラート
            let alert = UIAlertController()
            alert.title = "初期化"
            alert.message = "カードを配り直します"
            alert.addAction(UIAlertAction(title: "OK", style: .default))
            present(alert, animated: true, completion: nil)
            //残り枚数をリセット
            count = EnumCard.Count
        }
        //残り枚数0以上
        else {
            //今回引いたカードを特定する変数
            var card = 0
            //乱数を0から残り枚数−1の範囲で発生させる
            let randInt = Int.random(in: 0 ..< count)
            //発生させた乱数までカウントする変数
            var i = 0
            repeat {
                //既に開かれたカードならcardだけを+1する
                while check[card] {
                    card += 1
                }
                //iが発生させた乱数になったらループを抜ける
                if i == randInt {
                    break
                //それ以外なら、i,card共に1加算
                } else {
                    i += 1
                    card += 1
                }
            //ブレイクするまで、ループする
            } while true
                        
            //新たに出たカード既に出ているとマークする
            check[card] = true

            //残り枚数を1減算
            count -= 1
            //残り枚数表示用ラベルに残り枚数を表示
            lblCount.text = count.description

            //ジョーカーの場合
            if card == 52 {
                lblCard.text = "JK"
                //ジョーカー以外の場合
            } else {
                //カード表示用ラベルにマークとナンバーを表示
                lblCard.text = mark[card / 13] + number[card % 13]
            }

            //カードによって定められた位置に表示
            switch card {
            case 0: lblKA.text = lblCard.text
            case 1: lblK2.text = lblCard.text
            case 2: lblK3.text = lblCard.text
            case 3: lblK4.text = lblCard.text
            case 4: lblK5.text = lblCard.text
            case 5: lblK6.text = lblCard.text
            case 6: lblK7.text = lblCard.text
            case 7: lblK8.text = lblCard.text
            case 8: lblK9.text = lblCard.text
            case 9: lblKT.text = lblCard.text
            case 10: lblKJ.text = lblCard.text
            case 11: lblKQ.text = lblCard.text
            default: lblKK.text = lblCard.text
            }
        }
    }
    
}

 115行目から118行目は、その上の処理をやってから減算した方が良いので、位置を変えました。

 問題の部分だけを抜き出します。

            //今回引いたカードを特定する変数
            var card = 0
            //乱数を0から残り枚数−1の範囲で発生させる
            let randInt = Int.random(in: 0 ..< count)
            //発生させた乱数までカウントする変数
            var i = 0
            repeat {
                //既に開かれたカードならcardだけを+1する
                while check[card] {
                    card += 1
                }
                //iが発生させた乱数になったらループを抜ける
                if i == randInt {
                    break
                //それ以外なら、i,card共に1加算
                } else {
                    i += 1
                    card += 1
                }
            //ブレイクするまで、ループする
            } while true

 通常版で書いたものを再掲します。

 ここは、非常に難しいので、どういうアルゴリズムかと言うと、乱数を発生させるのは0から残り枚数−1として発生させ、既に出ているカードは飛ばして、残っているカードだけを対象にして、発生した乱数分数えて、そのカードを引いたカードとするものです。これだけだと難しいので、仮に♣️A、♣️2、♣️5、♣️7、♣️T、♣️Jが既に開かれているとして、6枚出ているので残りの7枚が最大となるように乱数を発生させます(0〜6を出す)。ここで例えば3が出たとすると、♣️A、♣️2は飛ばして、♣️3が0、♣️4が1、♣️5は飛ばして、♣️6が2、♣️7は飛ばして、♣️8が3で、今回開かれるのは♣️8となります。

 というか、まだ引かれていないカードは、♣️3、♣️4、♣️6、♣️8、♣️9、♣️Q、♣️Kで、♣️3を0から始めて、♣️4は1で、3になるのは♣️8となります。この処理をプログラムで書いたのが、上の例です。

 別の書き方です。

            //今回引いたカードを特定する変数
            var card = 0
            //乱数を0から残り枚数−1の範囲で発生させる
            var randInt = Int.random(in: 0 ..< count)
            //発生させた乱数を減算していき0になったらループ終了
            while randInt > 0 {
                //既に出ているカードならcard + 1
                while check[card] {
                    card += 1
                }
                //まだ出ていないカードなら発生させた乱数 - 1してcard + 1する
                randInt -= 1
                card += 1
            }
            //cardが既に出ている場合があるので、出ていないカードが出るまでcard + 1
            while check[card] {
                card += 1
            }

 先程の例で考えます。仮に♣️A、♣️2、♣️5、♣️7、♣️T、♣️Jが既に開かれているとして、6枚出ているので残りの7枚が最大となるように乱数を発生させます(0〜6を出す)。ここで例えば3が出たとすると、3>0なのでループが続きます。card =0は♣️Aなので check[card]=trueでループが続きます。card=1となり、card=1は♣️2なので check[card]=trueでループが続きます。card=2となり、card=2は♣️3なので check[card]=falseとなり、ループを抜けます。ここで randIntが−1されて2となります。card=3となります。

2>0なのでループが続きます。card=3は♣️4なので check[card]=falseとなりループは抜けます。randIntが−1されて1となります。card=4となります。

1>0なのでループが続きます。card=4は♣️5なので check[card]=trueでループが続きます。card=5となり、card=5は♣️6なので check[card]=falseでループを抜けます。ここでrandIntが−1されて0となります。card=6となります。

0>0でないのでループを抜けます。

card=6は♣️7なので check[card]=trueでループが続きます。card=7となり、card=7は♣️8なので check[card]=falseでループを抜けます。

こうして card=7となり、♣️8が引かれました。

 別の書き方です。

            //今回引いたカードを特定する変数
            var card = 0
            //乱数を0から残り枚数−1の範囲で発生させる
            var randInt = Int.random(in: 0 ..< count)
            //発生させた乱数を減算していき0になったらループ終了
            while randInt >= 0 {
                //既に出ているカードならcard + 1
                while check[card] {
                    card += 1
                }
                //まだ出ていないカードなら発生させた乱数 - 1してcard + 1する
                //発生させた乱数 - 1
                randInt -= 1
                //発生させた乱数から減算したものが0以上ならcard + 1
                if randInt >= 0 {
                    card += 1
                }
            }

 これは一つ前と近いのですが、メインのループが終わった後、

            while check[card] {
                card += 1
            }

この処理があり、これはメインループの最初と同じです。なので、ループを1回多くする為に while randInt > 0 としていたのを while randInt >= 0 にしました。ただその場合、card += 1 されてしまい、card が1大きくなってしまいます。そこで、randInt を減算した後、if randInt >= 0 の条件を入れて、card が1大きくなってしまわない様にしています。

 通常版で書いたものを再掲します。

 ここは難しいので、先程の例で考えます。仮に♣️A、♣️2、♣️5、♣️7、♣️T、♣️Jが既に開かれているとして、6枚出ているので残りの7枚が最大となるように乱数を発生させます(0〜6を出す)。ここで例えば3が出たとすると、3>=0なのでループが続きます。card =0は♣️Aなので check[card]=trueでループが続きます。card=1となり、card=1は♣️2なので check[card]=trueでループが続きます。card=2となり、card=2は♣️3なので check[card]=falseとなり、ループを抜けます。ここで randIntが−1されて2となります。2>=0なので card=3となります。

2>=0なのでループが続きます。card=3は♣️4なので check[card]=falseとなりループは抜けます。randIntが−1されて1となります。1>=0なので card=4となります。

1>=0なのでループが続きます。card=4は♣️5なので check[card]=trueでループが続きます。card=5となり、card=5は♣️6なので check[card]=falseでループを抜けます。ここでrandIntが−1されて0となります。0>=0なので card=6となります。

0>=0なのでループが続きます。card=6は♣️7なので check[card]=trueでループが続きます。card=7となり、card=7は♣️8なので check[card]=falseでループを抜けます。ここでrandIntが−1されて−1となります。−1>=0ではないので card はそのままです。

−1>=0でないのでループを終了します。こうして card=7となり、♣️8が引かれました。

 この様にも書けます。

            //今回引いたカードを特定する変数
            var card = 0
            //乱数を0から残り枚数−1の範囲で発生させる
            let randInt = Int.random(in: 0 ..< count)
            //発生させた乱数分ループ
            for i in 0 ... randInt {
                //カードが既に出ていたらcard + 1
                while check[card] {
                    card += 1
                }
                //iがrandIntに達していなければcard + 1
                if i < randInt {
                    card += 1
                }
            }

 先程の例で考えます。仮に♣️A、♣️2、♣️5、♣️7、♣️T、♣️Jが既に開かれているとして、6枚出ているので残りの7枚が最大となるように乱数を発生させます(0〜6を出す)。ここで例えば3が出たとすると、iが0から3までループします。card =0は♣️Aなので check[card]=trueでループが続きます。card=1となり、card=1は♣️2なので check[card]=trueでループが続きます。card=2となり、card=2は♣️3なので check[card]=falseとなり、ループを抜けます。ここで i < randInt は0<3なので card=3となります。

i = 1 となりループが続きます。card=3は♣️4なので check[card]=falseとなりループは抜けます。i < randInt は1<3なので card=4となります。

i = 2 となりループが続きます。card=4は♣️5なので check[card]=trueでループが続きます。card=5となり、card=5は♣️6なので check[card]=falseでループを抜けます。ここで i < randIntは2<3なので card=6となります。

i = 3 となりループが続きます。card=6は♣️7なので check[card]=trueでループが続きます。card=7となり、card=7は♣️8なので check[card]=falseでループを抜けます。ここで i < randInt は3<3ではないので card はそのままです。

こうして card=7となり、♣️8が引かれました。

 ラベルをコードから生成させる例です。とりあえず♣️だけです。

//
//  ViewController.swift
//  Card
//
//  Created by 保 Tabei on 2024/09/25.
//

import UIKit

class ViewController: UIViewController {
    ///カード表示用ラベル
    @IBOutlet weak var lblCard: UILabel!
    ///残り枚数表示用ラベル
    @IBOutlet weak var lblCount: UILabel!
  
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        //ラベル配列を適切に配置する
        for i in 0 ..< EnumCard.Count {
            //ラベルを生成する
            let lbl = UILabel(frame: CGRectMake(0, 0, 200, 21))
            //ラベルの中心を指定する
            lbl.center = CGPointMake(160, 100 + CGFloat(i) * 30)
            //ラベルのテキスト配置を中央にする
            lbl.textAlignment = NSTextAlignment.center
            //ラベルの表示をカードが裏になっている様にする
            lbl.text = "⬛️"
            //ラベル配列に追加する
            eachCard += [lbl]
            //ラベルを画面上に配置する
            self.view.addSubview(lbl)
        }
    }

    ///マーク保持配列
    let mark: [String] = ["♣️","♦️","❤️","♠️"]
    ///ナンバー保持配列
    let number: [String] = ["A","2","3","4","5","6","7","8","9","T","J","Q","K"]

    ///カード枚数保持定数、今回は♣️限定
    enum EnumCard {
        static let Count = 13
    }
    
    ///既に出ているかチェックする、出ていたらtrue
    ///出ていなかったらfalse、とりあえず
    ///EnumCard.Count枚分falseで埋める
    var check = [Bool](repeating: false, count: EnumCard.Count)

    ///残り枚数を保持する変数
    var count = EnumCard.Count
    
    ///ラベル配列
    var eachCard: [UILabel] = []

    ///ボタン押下時処理
    @IBAction func btnGoTouch(_ sender: Any) {
        //残り枚数0なら初期化
        if count == 0 {
            //check配列を全部falseに
            //ラベル配列をカードが裏になるように表示
            for i in 0 ..< EnumCard.Count
            {
                check[i] = false
                eachCard[i].text = "⬛️"
            }

            //アラート
            let alert = UIAlertController()
            alert.title = "初期化"
            alert.message = "カードを配り直します"
            alert.addAction(UIAlertAction(title: "OK", style: .default))
            present(alert, animated: true, completion: nil)
            //残り枚数をリセット
            count = EnumCard.Count
        }
        //残り枚数0以上
        else {
            //今回引いたカードを特定する変数
            var card = 0
            //乱数を0から残り枚数−1の範囲で発生させる
            let randInt = Int.random(in: 0 ..< count)
            //発生させた乱数分ループ
            for i in 0 ... randInt {
                //カードが既に出ていたらcard + 1
                while check[card] {
                    card += 1
                }
                //iがrandIntに達していなければcard + 1
                if i < randInt {
                    card += 1
                }
            }
            //新たに出たカード既に出ているとマークする
            check[card] = true

            //残り枚数を1減算
            count -= 1
            //残り枚数表示用ラベルに残り枚数を表示
            lblCount.text = count.description

            //ジョーカーの場合
            if card == 52 {
                lblCard.text = "JK"
                //ジョーカー以外の場合
            } else {
                //カード表示用ラベルにマークとナンバーを表示
                lblCard.text = mark[card / 13] + number[card % 13]
            }

            //ラベル配列の定められた位置に表示
            eachCard[card].text = lblCard.text
        }
    }
    
}

 55行目でラベルの配列を定義しています。

 20行目から33行目で、ラベルを生成し、配列に加え、画面上に配置しています。

 配り直しの66行目で、全部のカードについて裏で初期化しています。

 113行目で、ラベル配列の定められた位置に出たカードを表示しています。

目次 通常版

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA