現在3マッチパズルゲームを開発しております。
ゲームを開始すると5×6のパネルの中に、4種類ほどの異なるパネルがランダムで並びます。
(例として、「剣」「盾」「コイン」「ポーション」とさせてください)
これをユーザーが消していってゲームが進行するのですが、
同じ種類のパネルを3つ以上選択した場合にその選択したパネルを消すことができます。
ななめに選択はできず、縦か横へ連続して3つ以上選択した場合に限ります。
こういう仕様ですので、ゲームを開始した際に、
「この5×6のパネルの中で最低でもどこか1箇所は、同じ種類のパネルが3つ以上連続している」
必要があるのですが、そのアルゴリズムが考えつかず、質問させていただいている次第です。
どなたか解決方法がお分かりの方がおられましたらご教示いただけないでしょうか?
よろしくお願いいたします。
【追記】
説明不足ですみません。
上記の、
「5×6のパネルの中で4種類ほどの異なるパネルがランダムで並んでおり、
最低でもどこか1箇所は、同じ種類のパネルが3つ以上連続している」
こちらを判定する Javascript での具体的なコードを示していただけると大変助かります。
【解決】
ご回答いただき、ありがとうございました。
現在みなさまからいただいたアドバイスを参考にコードを作成しております。
私の中で一番しっくりとくる回答をいただいた方をベストアンサーとさせていただきましたが、
その他の方からいただいた回答も大変参考になりました。
誠にありがとうございました。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答8件
0
ベストアンサー
DungeonRaid
はい、おっしゃる通り、DungeonRaid をかなり参考にしております。
下記リンク先を読んで、概ね要件は理解できました。
要件をまとめると、次のようになりますね。
- 指で上下左右に隣接するパネルをなぞる
- 指を離した段階で3つ以上のパネルが連結していれば連結パネルを消去し、空きスペースに上からパネルが落ちてくる
- 常に隣接パネル(3つ以上)が一つ以上存在しなければならない
パターン化
3つのパネルが隣接する組み合わせは次の6通りだけです。
■ ■ ■ ■■■ ■ ■■ ■ ■■ ■■ ■ ■■ ■
従って、上記6パターンに合致する隣接パネルが一つ以上存在する事が保証できれば良い、という事になります。
隣接パネルを探すには起点となる座標から上記パターンと一致するか、確認していけば良い。
各々で起点となる座標は3個あるので、単純計算で18通りありますが、計算できない量ではないでしょう。
そして、落ちてくるパネルのパターンですが、これは更に少なく、4つだけです。
■ ■ ■ ■■■ ■■ ■ ■■ ■
※なお、ここでは「4つ以上が連結した隣接パネル」を考慮する必要はありません。
3つのパネルが隣接すれば、詰みにならない事が確定するからです。
単純な解決法
さて、もうお分かりかと思いますが、パネルが落ちてきた結果、詰みにならない一番簡単な解決法は「落ちてくるパネルを全て同色にすること」です。
落ちてくるパネルは必ず隣接する為、同色にすれば必ず隣接パネルとして消せます。
ただし、この解決法はゲーム性を著しく損なう悪手です。
「落ちてくるパネルが同色になること」にユーザはすぐに気が付きますので、パネルが落ちてきた瞬間に落ちたパネルを指をなぞるだけの単純作業と化します。
常にパネルを消せる状態にするという性質上、「同時に消したパネル数が多い程点数が高くなる」のような点数制を採用している可能性が高い為、タイムアタックモードで制限時間内に点数を競うゲームであった場合に、「落ちてくるパネルを消すだけのユーザ」は4つ以上を消すユーザに点数で負けてしまうので、結果的には使われないでしょうが、それでも同色パネルが常に落ちてくるとランダム性が損なわれるので、ゲーム性は落ちてしまうと思います。
もう少し、煮詰めた解決法
とりあえずの対策としては、下記方法が考えられます。
- パネルが落ちる前に残りパネルで消せる状況か確認
- 消せるパネルが存在しないなら、空白のパネルに隣接したパネルと同色パネルを1~2個落として隣接パネルを完成させ、残りはランダムにパネルを選ぶ
隣接パネルは「パターン化」の方法を使って、18通りの組み合わせを試していきます。
ランダム性と難易度設定
ここまでは「パターン通りのパネル配置になっているか」で確認する方法を書いてきましたが、おそらく一番いいのは全ての隣接パネルをキャッシュしておき、パネルが消えて落ちる度に部分的に更新して、全体を常に把握しておくことです。
「単純な解決法」では単純ゲームと化してしまうリスクに触れましたが、単純に乱数でランダムにパネルを選んで最低ライン(一つは隣接パネルを作る)を守る方式だと、乱数に難易度が影響されて「時の運」に左右されすぎだからです。
出来れば、難易度も制作側のコントロール下にあってほしいものです。
隣接パネルを探す単純なアルゴリズムに次があります。
- 起点となる座標を決め、座標を配列(adjacentPanel)にキャッシュする
- 上下左右のパネルを確認し、同色パネルなら配列に座標をキャッシュする(この時、上下左右を確認した座標は配列 checkedPanel に格納しておく)
- adjacentPanel.length === checkedPanel.length になるまで 2. を繰り返す
上記作業を繰り返し、全ての adjacentPanel を配列 allPanel に格納します。
そして、パネルを消した時には消したパネルの上方に位置するパネルに隣接するパネルの情報を全て消去し、残ったパネルで隣接パネルがないか計算します。
隣接パネルが存在するなら、ランダムにパネルを落とし、そうでないなら、残ったパネルに隣接するパネルを落とします。
最後に落としたパネルと隣接するパネルを含めて再計算します。
汎用性を上げる為には、配列 allPanel から任意の座標を含んだ adjacentPanel を返す関数を作っておくと良いでしょう。
この方法を採用した場合、常にN個以上の隣接パネルが存在する状態を作り出すことが可能となります。
パネルを落とすたびに全パネルを計算する方法も勿論ありますが、それではコストが高いので、再計算する範囲を最小限に抑えるのがこの方法の良いところです。
ユーザに難易度選択をさせる事が期待されるゲームなのであれば、下記の方式で難易度設定する事が可能です。
- Hard … 常に1個以上の隣接パネルが存在する
- Normal … 常に3個以上の隣接パネルが存在する
- Easy … 常に5個以上の隣接パネルが存在する
Re: clark さん
投稿2017/12/21 14:55
編集2017/12/22 02:31総合スコア18156
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/22 00:45
2017/12/22 01:43 編集
2017/12/22 04:37
2017/12/22 08:44
0
興味深いですね。
DungeonRaidのようなゲームだと思いますが、3マッチである以上、補充パネルは3枚以上で縦横につながった状態で落ちてくるはずです。
ですので、補充パネルに3枚連結を仕込めば、手詰まりはありえないということになります。
以上の前提から、
・補充前が手詰まり → 3枚連結を仕込む
・それ以外 → ランダム
で問題ないはずです。
なので、必要な処理は、盤面が手詰まりかどうか判定すればよいので、
1.あるパネルの隣接パネルに同じパネルが2枚以上ある → 手詰まりではない
2.あるパネルの隣接パネルに同じパネルが2枚以上ない → 次のパネルを照会
という単純なものになるのではないかと思います。
なお、初期盤面の問題も同様で、手詰まりだったら連結3枚を差し替えれば済みます。
投稿2017/12/20 08:59
総合スコア35865
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/20 09:15
2017/12/20 10:07
0
多分パネルは消したあとに上から補充されるんですよね?ゲームプレイ中に「手詰まり」が起きたとき、おそらくシャッフルや全入れ替えのような処理をすることになると思います。
だったら、「手詰まりかどうか」を判定する処理、および「シャッフルや全入れ替え」の処理は必ず必要になりますよね?その2つの処理があれば、無理に「絶対1箇所は消せる場所があるパターンをつくるアルゴリズム」を考える必要はなく、「初期配置」で**「手詰まりかどうか」を確認して、「手詰まり」でなくなる状態まで「シャッフルや全入れ替え」を繰り返せば要望の「この5×6のパネルの中で最低でもどこか1箇所は、同じ種類のパネルが3つ以上連続している」**ということが実現できますよね?
JavaScript
1var panels; // 5x6のパネルの入った配列 2 3// 配列の左上から「右に3つ」、または「下に3つ」連続したものを探す 4var find = false; // 見つかったかどうか 5for(var i = 0; i < 5 && !find; i++) { // 縦 6 for(var j = 0; j < 6 && !find; j++) { // 横 7 var type = panels[i][j]; // 剣、盾、コイン、ポーションのいずれか 8 // 横方向に+1、+2した場所と同じかどうかを調べる 9 if(j <= 6 - 3 && panels[i][j + 1] == type && panels[i][j + 2] == type) { 10 find = true; 11 } 12 // 縦方向に+1、+2した場所と同じかどうかを調べる 13 else if(i <= 5 - 3 && panels[i + 1][j] == type && panels[i + 1][j] == type) { 14 find = true; 15 } 16 } 17} 18if(!find) { 19 // 1箇所も連続した場所が見つからない 20}
投稿2017/12/20 04:16
編集2017/12/20 06:27総合スコア9206
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/20 06:07
2017/12/20 06:15
2017/12/20 06:27
2017/12/20 06:41
2017/12/20 06:52
2017/12/20 07:04
2017/12/20 07:08
2017/12/20 07:28
2017/12/20 07:34
2017/12/20 07:42
2017/12/20 07:46
2017/12/20 07:57
0
いちばん手っ取り早い方法としては、「まず最初に3連続をランダムな箇所・色で入れておいて、それからランダムに残りを埋める」という手法があります。
Windowsのマインスイーパーの場合、1回目は絶対に地雷を踏まないようになっていますが、クリックした瞬間に生成する、あるいは1回目のクリックで地雷があればどこかに移動する、といった方法で対応しているものと思われます。
投稿2017/12/20 04:06
総合スコア145121
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/20 06:02
2017/12/20 06:09
2017/12/20 06:25
2017/12/20 06:51
2017/12/20 07:09
2017/12/20 09:19
2017/12/20 09:30
0
「連続したパネルを消す処理は出来ている」ということは、
「指定した位置のパネルが連続しているかの判定は出来ている」ということですよね?
ということは
「盤面のマス1つ1つに対して判定を行う」ことで、1つ以上連続があるかどうかは分かります。
(最小1回、最大27回で完了します)
それを踏まえた流れは以下の通り。
- パネルが消される。(=穴が開く)
- 穴部分に入る予定のパネルをランダムに決める。
- 全てのマスに対して連続判定。→もし1つ以上連続部分があれば6へ。
- (連続部分が無い場合)穴の周囲の1マスをランダムに決める。
- 決めたマスと連続するように、「決めたマスの隣」と「更にその隣」のパネルを、決めたマスのパネルに変更。
- 実際に穴に入る部分のパネルを表示する。
とはいえ、全てのマスに対して連続判定を行うのは重いので、
n回に1回は全体の判定をせずに「穴の周囲を1つ決定→決めたマスと連続するようにパネル指定→生成」でもいいかもしれません。
つまり「新たに表示されるパネルは必ず消せる」状態になるのですが、ゲームバランスによって確率調整が必要かと思います。
そして上記が実用に足る速度が出せるかは分からないですし、今思い付いたアイデアなので参考までに。
投稿2017/12/20 08:10
総合スコア11425
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/12/20 09:37
2017/12/20 10:47
0
補填のこと等は置いといて、要件としての「判定ロジック」を書いてみます。
5*6程度ならば1パネルづつ、前後左右に同じパネルが配置されているかチェックして2つ以上同じものがあればtrue、とかどうでしょうか。
L時のような3つ揃いも対応できていると思います。
javascript
1//4種類の数値(剣」「盾」「コイン」「ポーション」に置き換えてください)をつかった行列データを定義 2//今回は4,5行目あたりに2の当たりが仕込まれています 3const mtx56 = [ 4 [ 1, 1, 2, 3, 2 ], 5 [ 2, 4, 1, 2, 1 ], 6 [ 2, 1, 4, 3, 2 ], 7 [ 4, 1, 3, 1, 2 ], 8 [ 3, 2, 2, 3, 4 ], 9 [ 1, 1, 2, 3, 4 ], 10]; 11 12/** 2次元配列のindexの値を取得 */ 13function get2(arr, x, y){ 14 return arr && arr[x] && arr[x][y] ? arr[x][y] : undefined; 15} 16 17/** 指定座標の前後左右の値をチェックする */ 18function check( cell, i, j ){ 19 //前後左右の座標値を設定 20 const around = { 21 top : [i-1, j], 22 right: [i, j+1], 23 bottom: [i+1, j], 24 left: [i, j-1], 25 }; 26 //マトリックスデータから前後左右の値を収集 27 const values = [ 28 get2(mtx56, around.top[0] , around.top[1] ), 29 get2(mtx56, around.right[0] , around.right[1] ), 30 get2(mtx56, around.bottom[0] , around.bottom[1] ), 31 get2(mtx56, around.left[0] , around.left[1] ), 32 ]; 33 //チェック中値と同じ値だけ抽出し、2件以上ならばtrue 34 return values.filter( val => val === cell ).length >= 2; 35} 36 37console.time( 'check' ); 38//メイン処理開始( 実装時は関数化 ) 39const isSafe = mtx56.some((row, i) =>{ 40 return row.some( (cell, j) => check(cell, i, j) ) 41}); 42 43console.timeEnd( 'check' ); 44console.log(isSafe);
投稿2017/12/20 09:23
編集2017/12/20 09:32総合スコア764
0
素朴な疑問ですが、今見えているパネルパターンの中に3つ以上の同じ絵柄の連続した状態があるかどうかをなぜ知る必要があるのでしょうか?
ゲーム進行中においても「手詰まりが起きないように配置する」ということですが・・・ゲームなのだから「手詰まり=負け」もあるんですよね?
最初の一回は「最低でもどこか1箇所は、同じ種類のパネルが3つ以上連続している必要がある」べきですが、その後のゲーム進行(消したところを埋める絵柄の発生)はランダムでいいのではないでしょうか?
1回で終わってしまっても、それは「運が無かったですね!次のゲームでがんばってください」でよくないですか?
?ばかりですみません・・・
「どこを消せばゲームを続けていくことができるか?」というユーザーの戦略性こそがこのゲームの面白さのように思います
なので、maisumakunさんのおっしゃるように
「まず最初に3連続をランダムな箇所・色で入れておいて、それからランダムに残りを埋める」
ということだけしてあげればよいのではないでしょうか
もう少しゲーム性(面白さ)を出すなら、一定の頻度で追加されるパネルの絵柄が揃ったものを出してあげたりすると、ユーザーも「ラッキー!」と感じてもっと続けたいと思ってくれるかも
それとも、絶対終わらないゲームを作りたいってことなのでしょうか
投稿2017/12/20 09:35
総合スコア3111
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。