条件付きの乱数の作り方を質問させてください
解決済
回答 5
投稿
- 評価
- クリップ 0
- VIEW 1,937
条件付きの乱数の作り方を質問させてください。
スマホのパズルゲームを製作しております。(初心者、unity5.6使用)
10ピースのパズルがあり、
ゲーム開始時に、パズルピースをシャッフルします。
パズルピース
|1|2|3|4|5|6|7|8|9|10|
スロット
|A|B|C|D|E|F|G|H|I|J|
スロット「A」に対しては「1」が正解でスロット「B」に対しては「2」が正解とします。(同様に「J」まで続く)
(スロットは固定で、パズルピースがシャッフルされます)
その際に問題になる条件が、
シャッフル時に、偶然に正解位置に割り振られる事を避けたい、という条件です。
つまり、スロット「A」には「1」以外を、スロット「B」には「2」以外を割り振りたいです。
(もちろん数字の重複も避けたいです)
この場合、
下記のような、正解位置を避けたシャッフルパターンを手動で作るのがいいでしょうか?
例えば
|10|6|2|1|7|8|6|4|5|9|
のような数列をあらかじめ5パターンくらい用意して、
それをランダムに使い回すのがいいでしょうか?
(自分で作った数列5パターンをシーンロード時に
5パターンの中からランダムにどれかを選択し使用する)
または
正解位置を避けながら、なおかつ同じ数字の重複も避けたの乱数の制作はC#で可能でしょうか?
※シーンが進むにつれてパズルのピースは増えて50個~60個になる予定です。
現状のプログラムの状態
1 まずシャッフルしたパズルピースを実際にシーン上に配置
2 シャッフル時に偶然に正解位置に置かれていないかを、コリジョン判定を利用して調べる
3 偶然に正解があった場合はシーンをリロードして再シャッフル
(全部のピースが完全に正解位置を避けるまで何度もシャッフルする)
というプログラムを作ったのですが、
この方法ですとスマホの実機上で実行した場合、
特にパズルピースの数が多い場合に、実機のメモリの消費が激しいようなので、
メモリ消費の少ない方法を探しております。
ご教授よろしくおねがいいたします。
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
動作未検証&どこまでメモリ消費するか分かりませんが、以下はいかがでしょう?
- 最初に正解のスロットにピースをはめる。
- そこから1スロット毎に、別のランダムなスロットとピースを入れ替える。
2-1. ただし入れ替えた結果どちらかのスロットが正解となる時は次のスロットを入れ替え先とする。次のスロットも正解になる場合は更に次のスロットを入れ替え先とする。
2-2. 最後までスロットが選択出来ず、自分のスロットに正解ではないピースが入っていれば2へ戻る。 - 並び替え後の配列の通りにオブジェクトを配置する。
動作例
・開始時(正解):分かりやすいように数を減らしています
|A-1|B-2|C-3|
↓
・スロットAのピースと、別のランダムなスロット(Cが選ばれたとする)のピースを入れ替え
(どちらも正解ピースではないことをチェック→問題無し)
|A-3|B-2|C-1|
↓
・スロットBのピースと、別のランダムなスロット(Aが選ばれたとする)のピースを入れ替え
(どちらも正解ピースではないことをチェック→問題無し)
|A-2|B-3|C-1|
↓
・スロットCのピースと、別のランダムなスロット(Aが選ばれたとする)のピースを入れ替え
Aと入れ替えるとAが正解になってしまうので次のスロットを見る
→Bと入れ替えるとCが正解になってしまうので次のスロットを見る
→最後のスロットまで来たが、スロットC(自分自身)が正解ではないので問題無し(入れ替えはしない)
|A-2|B-3|C-1|
↓
・全てのスロットを入れ替え終わったのでループ処理終了
↓
・出来上がった順にピースを配置すればOK
|2|3|1|
※速度や負荷を考慮すると入れ替え前に「現在のスロットに入っているピースが正解ではない場合、入れ替えを行わない」という判定を入れてもいいかもしれません。
※下手にやると「終了しない処理」になりそうなのでご注意ください。「n回ループしてたら最初からやり直し」とか必要かも。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
完全順列(derangement)というやつですね。
ランダムなderangementの生成はこの質問で紹介されてる論文が参考になるのではないでしょうか。
https://math.stackexchange.com/questions/302057/generating-a-random-derangement
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
+1
正解あり
↓
正解があったピースと隣のピースを入れ替える
↓
再評価
というのはどうでしょう。
交換された2つの箇所は必ず正解でない組み合わせになるはず。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
追記:以下は回答者の勘違いにより的を外した内容になっています。読み飛ばしてくださるようお願いします。
次のようなアルゴリズムではダメでしょうか。
n個のスロットがあるとする。
m<=1, m=>nの範囲で以下を繰返す。
- 乱数を1からn-1までの間で生成する。これをrとする。
- m==r ならば r=r+1とする。
- スロットmの値はrとする。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
0
C#やったことないんで的外れになるかもしれませんが・・・
あとシャッフルというのが視覚的効果を伴うものであれば役に立たないかもしれません。
1.一旦パズルピースを未使用リストみたいなのに格納
2.頭から順にシャッフルしたパズルピースを決めていくとして、
3.その位置の正解が未使用リストに残っている場合は、そのピースをリストから退避
4.残ったリストの範囲で乱数を取得してピースを決定
5.位置が決定されたピースは未使用リストから削除
6.退避していたピースがあれば未使用リストに戻す
7.次の位置へ
8.3に戻る
これを全てピースが決まるまで繰り返します。
C#でリスト内のオブジェクトを特定する際にかかるコストがわからないので
もしかしたら処理時間が大変なことになるかもしれませんが・・・(60程度問題ないですよね?)
メモリ消費についてはわからないです。
繰り返し処理内で変にインスタンスを生成しなければそこまで大量に消費することはないと思いますが・・・
突っ込みどころがあれば教えてください。
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.32%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2017/10/11 20:20 編集
2017/10/11 20:39
シーンにパズルピースを配置してから、ピースを入れ替えするのではなく、
メモリ消費を考慮して、
完全順列(下記の方のご回答ではじめてこの言葉を知りました)の
乱数を作った後に、
シーンにパズルピースを配置したいと考えております。
2017/10/11 20:54 編集
「現状のやり方」でメモリを多く消費しているのは恐らく「シーンリロード」の部分です。
なので「コリジョン判定後に再シャッフル」の部分をシーンリロードで行わない(メソッド再呼び出しだけにする)だけでも『メモリ消費については』改善すると思います。
また、コリジョン判定も負荷となるので、それをやめるだけでも高速化します。
上記を踏まえると、最小のスロット数にもよりますが下手なアルゴリズムを組むより「ランダムな配列作成→正解と合致しているかチェック→合致していたら再シャッフル」の繰り返しの方が早いかもしれないです。
(deigoさんのロジックを更に追加すると無限ループも防げるかと思います)
2017/10/12 03:12
コリジョン判定を使用せずにパズルピースが正解スロットに置かれているかどうかを
判定する方法はあるのでしょうか?
2017/10/12 09:47
(実際の動きがどうなのかにもよるので、上記は的外れな可能性があります。現状のスクリプトを提示してもらえればもっと的確に言えます)
2017/10/12 09:53
各piece1からpiece10まで同名のオブジェクトがそれぞれ2つ存在しております。(piece1が2つ、piece2も2つ、、、)
例えばpiece1が正解位置に来てしまったのでpiece2と入れ替えしたい場合、
2つのオブジェクトを取得するのに手間取っております。
piece1は、GameObject irekae1 = other.gameObject; と指定できますが、
piece2は同名オブジェクトが2つ存在しているので名前で検索できないと思われます。
また、シャッフルして階層も変わるのでパスでの指定もできません。
FindGameObjectWithTagで検索し、(これだと10個の全部のpieceが該当してしまいますが)
何かよい方法はありませんでしょうか? すいませんが、可能でしたらご教授おねがいいたします。
2017/10/12 10:02
別名には出来ないのでしょうか?
また、「FindGameObjectWithTagで検索→ヒットしたオブジェクトの名前で検索」という二段階での判定でもいいと出来ます。
一番良いのは、最初に
GameObject[] pieces;
GameObject[] slots;
という形でオブジェクトの配列を持っておき、スクリプトで利用することです。
(Find系メソッドもやっぱり負荷が高い為)
2017/10/12 10:22
void OnTriggerStay2D(Collider2D other){
if (other.gameObject.name == gameObject.name) //ピースのpiece1と背景のpiece1の重なり
{正解時の動作}
}という形を利用していおりました。
違う方法を探してみたいと思います。
今回は大変ありがとうございました。
2017/10/12 10:47
オブジェクト名判定で単純にやるなら
ピースの名前:piece1、piece2……
スロットの名前:slot_piece1、slot_piece2……
みたいにしておき、
if ("slot_"+other.gameObject.name == gameObject.name) {
//正解時の動作
}
という形でも出来ると思います。
(これならGameObject.Find("piece1")で1つしかヒットしない)
色々書いてしまいましたが「とりあえず動けばいいんだよ」等の都合があると思うので、検討の上お好きな方法でやってもらえればいいと思います。
2017/10/12 18:28
if ("slot_"+other.gameObject.name == gameObject.name) をやってみましたが動作しませんでした。
if (other.gameObject.name == gameObject.name && other.gameObject.tag == "slot")
との二段階での判定なら動きました。できましたら同名オブジェクトは避けてコリジョン判定を"slot_"+のように”同じ名前を含む”で動作できるとベストなんですが無理のようです。
2017/10/17 20:47
コリジョン判定条件の「名前の一部が同じ場合に命令実行」は
>>この指定方法>> if ("slot_"+other.gameObject.name == gameObject.name)
sakura_hanaさんのご指摘どおり、作動しました。すいませんでした。ご教授ありがとうございました。