新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行目で、ラベル配列の定められた位置に出たカードを表示しています。