質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

2回答

1095閲覧

[Unity]ボードゲームにおける詰み(駒を置けない状態)の判断方法について意見を聞きたい

yuki_pooh117

総合スコア16

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

3クリップ

投稿2019/01/09 10:36

前提・実現したいこと

現在Unityを使ってBlokusというボードゲームを製作しております。
Blokusというゲームについて

ゲーム画面
盤面、盤面上のピースはタイルマップによって描画されています。画像右側の手持ちのピースはスプライトによって描画されています。画像の通り、手持ちのピースをドラッグして盤面にゲームルール(同じ色の斜めにしか置けずとなりに同じ色があったら置けない)の通りに設置するところまでは実装できました。
ここで本題ですが、あるプレイヤーの番になったときに盤面でこれ以上置けるところがない場合、そのプレイヤーのターンを強制的にパスさせることや、置ける場所がないためゲーム終了、といった具合に詰みの状態の判定方法がこの先必須となっております。ただ、21個のピースはすべて形が違く、回転もできるように実装したので、恥ずかしながら詰みの判定の実装方法がよくわからず困っております。実装の方針やアイデアなどをご教授いただければ幸いです。

発生している問題・エラーメッセージ

詰みの状態の判定方法を知りたい

現在実装しているピース設置時の判定

C#

1public void onDragEnd() 2 { 3 Debug.Log("EndDrag"); 4 if (isPutable()) 5 { 6 Debug.Log("Putable!"); 7 8 switch (gameManager_Script.turn) 9 { 10 case "red": 11 drawTile = redTile; 12 13 break; 14 case "blue": 15 drawTile = blueTile; 16 17 break; 18 19 case "green": 20 drawTile = greenTile; 21 22 break; 23 24 case "yellow": 25 drawTile = yellowTile; 26 break; 27 } 28 29 30 for (int i = 0; i < this.transform.childCount; i++) //親オブジェクトが持っている子オブジェクトの数 回 31 { 32 position = this.transform.GetChild(i).gameObject.transform.position; //子オブジェクトのポジションを代入 33 //position = transform.localPosition; //このスクリプトがアタッチされたオブジェクトのローカル座標 34 Vector3Int cellPos = tileManager.tilemap.WorldToCell(position); //positionと被っているタイルのベクトル取得 35 tileManager.drawTile(cellPos, drawTile); //tileManager.csのメソッド 36 //Destroy(this.gameObject); //gameobject削除 37 38 } 39 gameObject.SetActive(false); 40 switch (gameManager_Script.turn) 41 { 42 case "red": 43 redPlayer_Script.SetIsUsed(GetNumberOfArray(), true); 44 45 46 gameManager_Script.turn = "blue"; 47 break; 48 case "blue": 49 bluePlayer_Script.SetIsUsed(GetNumberOfArray(), true); 50 51 52 gameManager_Script.turn = "green"; 53 break; 54 case "green": 55 greenPlayer_Script.SetIsUsed(GetNumberOfArray(), true); 56 57 58 gameManager_Script.turn = "yellow"; 59 break; 60 61 case "yellow": 62 yellowPlayer_Script.SetIsUsed(GetNumberOfArray(), true); 63 64 65 66 gameManager_Script.turn = "red"; 67 break; 68 } 69 gameManager_Script.turnChanged = true; 70 }else //置けなかったら 71 { 72 this.transform.position = gameManager_Script.prefabPos[GetNumberOfArray()] + tileManager.tilemap.transform.position; 73 } 74 75 uiManager_Script.rotateRightBtn.gameObject.SetActive(false); 76 uiManager_Script.rotateLeftBtn.gameObject.SetActive(false); 77 } 78 79public bool isPutable() 80 { 81 int childAmount = this.transform.childCount; 82 int decider = childAmount; 83 Vector3Int[] positions = new Vector3Int[childAmount]; 84 for(int i = 0; i < childAmount; i++) 85 { 86 position = this.transform.GetChild(i).gameObject.transform.position; //子オブジェクトのポジションを代入 87 positions[i] = tileManager.tilemap.WorldToCell(position); //positionと被っているタイルのベクトルを配列に格納 88 } 89 90 for(int i = 0; i < childAmount; i++) 91 { 92 if (isPutbableChecker(positions[i]) == true) 93 { 94 decider -= 1; 95 } 96 //一つ一つのタイルについて回りを調べ 97 //おけるようならchildAmount - 1 をしていく 98 //最終的にchildAmountが0になったらtrueをreturnする 99 } 100 101 if(decider == 0 && isSlantingSame == true) 102 { 103 Debug.Log(isSlantingSame); 104 isSlantingSame = false; 105 return true; 106 107 } 108 109 return false; 110 } 111 112 public bool isPutbableChecker(Vector3Int pos) //一つのタイルについておくことができるかを確認 第一引数:おこうとしているタイルのpos 113 { 114 if (tileManager.tilemap.GetSprite(pos) == sprite_background) //posの位置のタイルのスプライトが背景のだったら 115 { 116 Debug.Log("背景だった"); 117 if(tileManager.tilemap.GetSprite(pos + up) != this.transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite //posの一つ上のタイルがおこうとしているタイルと違うなら 118 && tileManager.tilemap.GetSprite(pos + down) != this.transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite 119 && tileManager.tilemap.GetSprite(pos + right) != this.transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite 120 && tileManager.tilemap.GetSprite(pos + left) != this.transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite 121 ) //上下左右に同じスプライトのタイルがなかったら進む 122 { 123 Debug.Log("隣に同じタイルはなかった"); 124 if(tileManager.tilemap.GetSprite(pos + up + right) == this.transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite 125 || tileManager.tilemap.GetSprite(pos + up + left) == this.transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite 126 || tileManager.tilemap.GetSprite(pos + down + right) == this.transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite 127 || tileManager.tilemap.GetSprite(pos + down + left) == this.transform.GetChild(0).gameObject.GetComponent<SpriteRenderer>().sprite 128 ) //ななめにあるスプライトが同じなら進む 129 { 130 Debug.Log("斜めに同じスプライトがあった"); 131 isSlantingSame = true; 132 } 133 134 135 return true; //斜めは関係なしに上下左右に同じスプライトがないところまで進めたらtrueを返す 136 } 137 } 138 139 140 return false; //条件を満たさなかった 141 }

試したこと

パスするボタンを設置してユーザーにパスさせるということも考えましたが、それでは、パソコンでやるメリットが減ってしまったり、これから先敵AIを作成する際に詰まると思いやめました。

補足情報(FW/ツールのバージョンなど)

Unityのバージョンは2018.3.0f2です。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

papinianus

2019/01/09 10:48

ルールを伺います。図では盤外に緑しかないのですが、プレイヤーは任意の形の任意の色のブロックを置けるのですか?それとも予め手持ちのブロックが決まっていますか?
yuki_pooh117

2019/01/09 10:59

質問ありがとうございます。あらかじめ手持ちのブロックは決まっています。ブロックの種類は質問の中で載せたリンク先にございます。ゲームスタート時に最初のプレイヤー(赤、青、緑、黄色のどれか)をランダムで決め、そこからは時計回りで順番で置いていきます。ブロックを置くことができたら次のプレイヤーの番になるようになっています。プレイヤーごとに使ったブロックの情報が記録されており、各プレイヤーが21個のブロックを使えるようになっています。
guest

回答2

0

すべてのマス目に対して、すべての方向で置いてみる、ということをして、置けるところを探せばいいかと。
これぐらいのマス目ならそんなに時間がかからずに実行できるでしょ

投稿2019/01/09 10:49

y_waiwai

総合スコア87774

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yuki_pooh117

2019/01/09 11:27

回答ありがとうございます。やはりそれしかないんですかね。勝手に時間がかかるものだと思い込んでました。
y_waiwai

2019/01/09 11:31

まずはこれで実装できないことには話になりません。 これがきちんとできた上で、時間短縮やら何やら探っていけばいいでしょ
R.Mizukami

2019/01/09 16:42

400マス*20ピース以下*4方向の回転 の全パターンを網羅しても、10^4~10^5程度の計算量です。現代のPCは各スレッドが 毎秒 10^9 程度の処理をこなしますので、個別の判定に多少時間をかけても、我々の瞬きより早い時間で計算が終了することと思います。
yuki_pooh117

2019/01/10 08:40

y_waiwai様、仰る通りですね。ある程度目処が立ったのでまずは時間はあまり気にせずに実装してみます!回答ありがとうございました。
yuki_pooh117

2019/01/10 08:46

R.Mizukami様、回答ありがとうございます。そうなんですか!普通科の高校なのであまりそういった知識はないので助かります。一つ質問なのですがDebug.Log()って重い処理なのでしょうか?昨日ループ内で使用した際に処理がとても遅く、Debug.Log()をコメント化してみると処理は大幅に早くなりました。
R.Mizukami

2019/01/10 11:22

- ログを保持するために適宜メモリを確保しなければなりませんし、 - 出力先は多くの場合他のプロセスからアクセスされる可能性があるため同時に操作があっても正確に出力がなされるように整合性を保たなければならないし、 - Unity のログコンソールの色を付けるタグの効果を確認するため構文解析をする必要がある ので、1ステップに見えてかなりのステップを消費していると思います。私も専門ではないのでただの想像になりますが、ご参考までに。
guest

0

ベストアンサー

少し効率的にするのであれば、下記の方法はいかがでしょうか。

  1. 全てのマスにplaceable(仮)フラグをtrueにする
  2. すでにピースが配置されているマスのフラグをfalseにする
  3. 「置けないマス(同じ色のブロックに隣り合ったマス)」のフラグをfalseにする
  4. 全てのtrueのマスに残りのピースの配置を試み、成功であれば「詰みなし」判定でreturn、最後まで失敗すれば「詰み」判定

この時、それぞれのピースに原点みたいなもの(ブロックが存在するマス)を設定しておけば便利かもしれませんね。

追記
上記に加え、「そこに置いてもそのピースの大きさだと自分のブロックの色のところまで届かないよ」というマスもあらかじめfalseにしてしまった方が後々の計算量は少なくなりそうです。

追記2
ここまでやるなら別のアルゴリズムの方が効率的かと思い、図示してみました。
上の方も仰っている通り、まずはしらみ潰しで調べる方法を実装してみるのが良いかと思います。その際に関数の切り分けなどを考え、後々効率化しやすい用に設計してみてください。
イメージ説明

投稿2019/01/09 15:08

編集2019/01/10 13:46
Kapustin

総合スコア1186

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

yuki_pooh117

2019/01/10 08:59

回答ありがとうございます。なるほど。フラグは二次元配列で対応させられるか試してみますね。追記に関してですが、全てのピースが3*3の範囲内に収まるので、ブロックから3マス以内に同じ色がなければフラグをfalseにするという解釈で合っていますか?
Kapustin

2019/01/10 13:13

原点をどこにするかにもよりますが、最大で3x3の場合は、ピースの原点を中央として7x7の範囲にブロックがなければ少なくとも要件は満たさないはずです。(図を書いて確認してみてください) そう考えるとすでに置いてあるブロックから数えて候補を潰していく方が良さそうですね。
yuki_pooh117

2019/01/13 05:03

しらみつぶしでの詰みの判定の実装が完了いたしましたので、ベストアンサーにさせていただきました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問