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

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

ただいまの
回答率

87.80%

[Java]ゲームでよくあるアイテム生産の仕組みが分かりません

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 3,574

score 13

Java・プログラミングともに初心者です。
Swingで簡単なボードゲームの盤を作ったことがあるくらいです。(オセロや五目並べのような)

ゲームでよくあるアイテム生産のような仕組みを作りたいのですが、どう考えたらいいのか分かりません。
イメージとしては、冷蔵庫に入っている食材をもとに、レシピ本を見て料理を作るような感じです。

食材…食材クラスのオブジェクト(食材ID(int)、食材名、食材の説明文を持っている)
食材リスト…(食材ID=indexのArrayListの要素に、IDに対応した食材オブジェクトが入っている)
冷蔵庫…食材が入っている。(食材IDをkey、所持数をvalueとしたMapで管理している)
レシピ本…料理に必要な複数の食材とその個数が書いてある。(簡単に考えるため、料理法は考慮に入れない)

レシピ本を見て料理するところのイメージがうまくつかめません。
必要な食材の種類、個数は料理によって違うだろうし、それをどうやって冷蔵庫の在庫と照らし合わせたらいいのか、たくさんのレシピの中から今持っている在庫で作れる料理を検索したりするのもイメージできません。

漠然とした質問で申し訳ないですが、考え方のヒントだけでもいただけると嬉しいです。
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+3

冷蔵庫 := Map<食材ID, 所持数>
レシピ := Map<食材ID, 必要数>
レシピ本 := Array<レシピ>

ここまではよろしいですよね。
(ここではJavaではなく、アイディアを疑似言語で書いています。以下も同様。)

冷蔵庫をクラスとして、こんな動きのメソッドを作り付けていきましょうか。
// このレシピを最大何皿作れるか
int HowManyCanMakeProductFrom(レシピ) {
  int result = int.max
  for ( [食材ID, 必要数] in レシピ) {
    int div = map.get(食材ID) / 必要数
    result = min(result, div);
  }
  return result;
}
レシピの各食材について必要数の何倍持っているかを判定して、その最小値が最大皿数です。
これが0なら、作れません。
そしたら、レシピ本の料理すべてについて試してみるメソッドが書けます。
// すべての料理について最大何皿作れるかまとめて回答する
Map<食材ID, 最大皿数> HowManyCanMakeProductFrom(レシピ本) {
  Map result;
  for ([レシピID, レシピ] in レシピ本) result.put(レシピID, HowManyCanMakeProductFrom(レシピ));
  return result;
}
プレーヤーが作りたいアイテムと個数を選んで実行したときには
bool CreateProduct(レシピ, 個数) {
  if (HowManyCanMakeProductFrom(レシピ) < 個数) return false; // 材料が足りてません!
  for ([食材ID, 必要数] in レシピ) {
    現在数 = map.get(食材ID);
    現在数 -= 必要数 * 個数;
    map.set(食材ID, 現在数);
  }
  return true; // 成功したから、アイテムリストに生成物を加えていいですよ!
}
なんてメソッドを呼ぶことになります。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/10 21:06

    1回分の判定のみならず、レシピ本すべてを試行する仕組みなども書いていただいて、ありがとうございました。
    メソッドの中身もシンプルで、まさにこんな風に書きたいと思ったものでした。
    教えていただいた内容を参考に、続きを作りたいと思います。

    キャンセル

+2

「食材」と「食材リスト」の関係のように,「レシピ」とその集合である「レシピ本」という考え方をするべきではないでしょうか.
レシピは料理の名前と,それに必要な食材と必要数の組み合わせを持たせます.これもMapの管理がいいのではないでしょうか.
各レシピで料理が作れるかは,レシピに載っている食材をkeyに冷蔵庫Mapから値を取り出し,数が足りているか判定.すべての食材について足りていれば「作れる」と判定できます.MapからはそのMapが保持している組み合わせを取り出すためのSet<Map.Entry<K, V>>をentrySet()メソッドで取り出して,forループで回せます.
メソッドのイメージとしてはこんな感じ…
boolean canCook(Map<Integer, Integer> recipe, Map<Integer, Integer> stock){
    for(Map.Entry<Integer, Integer> e : recipe.entrySet()){
        int need = e.getValue();
        Integer temp = stock.get(e.getKey());
        int have = temp != null ? temp.intValue() : 0;
        if(need > have) return false;
    }
    return true;
}

本題とは関係ないですが,冷蔵庫にKeyの食材が無かった場合にnullが返ってくるのは何かと面倒なので,get()をオーバーライドしてnullを0のIntegerにする手続きをするといいかもしれません.
Map<Integer, Integer> fridge = new HashMap<Integer, Integer>(){
    @Override
    public Integer get(Object key){
        Integer temp = super.get(key);
        return temp != null ? temp : new Integer(0);
    }
};

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/10 21:06

    ご回答ありがとうございます。
    仰るとおり、レシピ本はレシピの集合体ですね。クラスで食材の種類や個数を管理すればいいんですね。
    entrySet()を使ってfor文を書くのもとてもすっきりとして分かりやすかったです。

    キャンセル

+1

こんな感じに実装できます。
食べ物という変数はクラスを使うとうまい具合に実装できますね。

調理するときは、レシピを入れて冷蔵庫にある食べ物で足りるか
どうかを判別するようなメソッドを立てて、
大丈夫なら冷蔵庫の中身を消費、ダメなら冷蔵庫の中身は消費しない。

というような実装が良いと思います。

//食材
class food{
    int foodID;
    String foodName;
    String foodDescription;
    food(int id, String name, String description){
        //コンストラクタで値を格納
        foodID = id;
        foodName = name;
        foodDescription = description;
    }
}

//食材リスト
ArrayList<Integer> foodID = new ArrayList<Integer>();
ArrayList<food> foodInstance = new ArrayList<food>();
foodID.add(0);
foodInstance.add(new food(0, "トマト", "赤くておいしいトマト。リコピンたっぷり!"));
foodID.add(1);
foodInstance.add(new food(1, "きゅうり", "サクサク食感が癖になるきゅうり。あまり栄養は無いらしい。"));

//冷蔵庫 ID, 持ってる数
HashMap<Integer, Integer> refrage = new HashMap<Integer, Integer>();
refrage.put(0,10);
refrage.put(1,10);

//レシピ本
//複数のfoodIDと消費数が書いてある。
ArrayList recipe = new ArrayList(); //Object型で保存
int[] spendTomato = {0,3};
int[] cucumber = {1,2};
recipe.add(tomato);
recipe.add(cucumber);

//作る時
boolean makeDish(recipe){
    //冷蔵庫の中にレシピの数だけ食材があるかどうか確認する。
    //冷蔵庫の中はクラス変数として扱ってる前提
    boolean isSuccess = false;
    for(int i=0; i<recipe.size(); i++){
        int[] recipeElement = (int[]) recipe.get(i);    //id,数の順
        HashMap<Integer, Integer> refrageCopy = refrage.clone();    //配列系は参照代入なのでシャローコピーを作成
        if(refrageCopy.get(recipeElement[0]) >= recipeElement[1]){
            //例えば、IDが0だとしたら、 refrage.get(recipeElement[0]) は、 トマトの数を示します。
            //recipeElement[1] も、トマトの数を示します。
            //leftAmount は、 冷蔵庫の中身から、レシピの数だけ値を引いたものです。(残り)
            int leftAmount = refrageCopy.get(recipeElement[0]) - recipeElement[1];
            refrageCopy.put(recipeElement[0], leftAmount)
        } else {
            //どっかで足りない数があったら、ここまでの操作を取り消します。
            refrage = refrageCopy;
        }
        if(i == recipe.size()-1){
            //レシピ全部の調理ができたら、Copyに操作していた処理を本物に移し替えます。
            refrage = refrageCopy;
            //で、調理成功フラグを呼び出し元に返します。
            isSuccess = true;
        }
    }
    return isSuccess;
}

//呼び出す時
if (makeDish(recipe)){
    System.out.println("もぐもぐ。トマトキューリはおいしいなぁ");
} else {
    System.out.println("冷蔵庫に食材が足りなくて料理ができなかった・・");
}

それと、よくある失敗例として新しく宣言した配列やMapの変数に既存の配列を
代入したりすると、参照代入となってしまいます。

参照代入した変数に値を入れると、元の配列も一緒に変更されてしまうので、
(入れる場所が2箇所になった。みたいな感覚です。)
clone()メソッドを使ってシャローコピーを作ってあげるとちゃんとコピーが作れます。

データストラクチャーは上手に考えられているので、
ロジックは書けばなれますよ。

という感じです。

参考:
クラス ArrayList<E>
https://docs.oracle.com/javase/jp/6/api/java/util/ArrayList.html
要素の格納と取り出し
http://www.javadrive.jp/start/hashmap/index2.html

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2015/10/10 21:05

    最初から最後まで、詳しくありがとうございました。
    Mapのコピーを作っておいて、とりあえずそちらで処理をした後、全部の数が足りていたら本物に移し替えるというのは考えていなかったので、とても参考になりました。

    キャンセル

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

  • ただいまの回答率 87.80%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る