過去、Java + Springでのガチャコードをサンプルとして公開いたします。

「レアリティ -> カード抽選」の2段階抽選を行うシンプルなガチャという想定です。

開発環境はこんな感じでした。

  • Java 7
  • Spring Framework (Sprint 4)
  • Maven 3
  • commons-lang3
  • lombock

数年前のもののため、Java7です。

実装

pom.xml

dependencyにApache commonsのcommons-lang3を追加してください。

1<dependency>
2    <groupId>org.apache.commons</groupId>
3    <artifactId>commons-lang3</artifactId>
4    <version>3.0</version>
5</dependency>

Randomsクラス

抽選処理用のRandomsクラスです。

 1import java.util.Collection;
 2
 3import org.apache.commons.collections.CollectionUtils;
 4import org.apache.commons.lang3.RandomUtils;
 5
 6public class Randoms {
 7
 8    private Randoms() {
 9    }
10
11    public static <T> WeightedObject<T> detect(Collection<WeightedObject<T>> collection) {
12        if (CollectionUtils.isEmpty(collection)) {
13            return null;
14        }
15        int total = 0;
16        for (WeightedObject<T> weighted : collection) {
17            total += weighted.getWeight();
18        }
19        int random = RandomUtils.nextInt(0, total);
20        total = 0;
21        for (WeightedObject<T> weighted : collection) {
22            total += weighted.getWeight();
23            if (total > random) {
24                return weighted;
25            }
26        }
27        return null;
28    }
29
30    public static <T> WeightedObject<T> toWeightedObject(T object, Integer weight) {
31        return new WeightedObject<T>(object, weight);
32    }
33
34    public static class WeightedObject<T> {
35        public WeightedObject(T object, Integer weight) {
36            this.object = object;
37            this.weight = weight;
38        }
39
40        private T object;
41        private Integer weight;
42
43        public T getObject() {
44            return object;
45        }
46        public int getWeight() {
47            return weight;
48        }
49    }
50}

スキーマとValueObject

スキーマ

1次、2次抽選の排出率を設定するテーブルを用意します。

レアリティ設定テーブル(レアリティ設定ID, レアリティ, 重み)
カード設定テーブル(カード設定ID, レアリティ, カードID, 重み)

このほか、価格や排出設定を行うガチャ設定テーブルを用意するのが一般的だと思います。
このサンプルでは割愛いたします。

ValueObject

ガチャ処理用の構造体です。
lombockを使う前提の実装です。

また、複合主キーの場合にキーで1つの構造体を作る実装もあると思いますが、このサンプルでは簡略にいたしました。

GachaRarity.java

レアリティ設定テーブル用のValueObjectです。

 1import java.io.Serializable;
 2
 3import lombok.Data;
 4
 5@Data
 6public class GachaRarity implements Serializable {
 7    private static final long serialVersionUID = 1L;
 8
 9    private Integer id;
10    private Integer rarity;
11    private Integer weight;
12}

GachaCard.java

カード設定テーブル用のValueObjectです。

 1import java.io.Serializable;
 2
 3import lombok.Data;
 4
 5@Data
 6public class GachaCard implements Serializable {
 7
 8    private static final long serialVersionUID = 1L;
 9
10    private Integer id;
11    private Integer cardId;
12    private Integer rarity;
13    private Integer weight;
14}

ガチャ処理

lotを実行すると、引数に従って抽選処理を行う実装です。
レアリティ設定テーブル、カード設定テーブルのIDを事前に決定されるものといたします。

 1@Service
 2public class GachaService {
 3    @Autowired
 4    private GachaRarityDao gachaRarityDao;
 5    @Autowired
 6    private GachaCardDao   gachaCardDao;
 7
 8    private List<Integer> lot(Integer gachaRarityId, Integer gachaCardId, Integer lotCount) {
 9        // これが抽選結果を格納するリスト
10        List<Integer> newCardIds = new ArrayList<>();
11
12        // 連ガチャに対応するためループで指定回数繰り返す
13        for (int i=1; i<=lotCount.intValue(); i++) {
14            // 1次抽選でレアリティを決定する
15            List<GachaRarity> rarities = gachaRarityDao.findListById(gachaRarityId);
16            List<Randoms.WeightedObject<Integer>> rarityObjects = new ArrayList<Randoms.WeightedObject<Integer>>();
17            for (GachaRarity rare : rarities) {
18                rarityObjects.add(
19                    Randoms.toWeightedObject(rare.getRarity(), rare.getWeight())
20                );
21            }
22            Integer detectRarity = Randoms.detect(rarityObjects).getObject();
23
24            // 2次抽選でレアリティに紐づくカード群から1枚を決定し、返却用のリストにアドする
25            List<GachaCard> cards = gachaCardDao.findListByIdAndRarity(gachaCardId, detectRarity);
26            List<Randoms.WeightedObject<Integer>> cardObjects = new ArrayList<Randoms.WeightedObject<Integer>>();
27
28            for (GachaCard card : cards) {
29                rarityObjects.add(
30                    Randoms.toWeightedObject(card.getCardId(), card.getWeight())
31                );
32            }
33            newCardIds.add(
34                Randoms.detect(cardObjects).getObject()
35            );
36        }
37        return newCardIds;
38    }
39}

daoは、それぞれキーに従ってDBのSELECT結果をリストで返す関数が実装されているイメージです。
テーブルの内容はそうそう頻繁に変わるものではないと思いますので、Springの特性を活かし、一度ロードしたものはヒープに乗せておくと良いですね。

おわりに

また、改善の余地がたくさんあるコードですので、ぜひサンプルから発展させたコードを書いてください。

ガチャの施策については過去にこんな記事も書きましたので、よろしければご覧ください。

[Web]ソシャゲ『ガチャ』の施策と設計
atuweb 開発ブログ

WEB+DB PRESS Vol.92

近藤 宇智朗,大和田 純,谷口 禎英,後藤 利博,黒瀧 悠太,山下 和彦,河野 匡貴,古橋 貞之,瀬尾 直利,菅原 元気,吉川 崇倫,鈴木 康平,星 北斗,三宅 英明,長野 雅広,のざき ひろふみ,うらがみ,稲富 駿,伊藤 直也,うさみ けんた,丸山 晋平,中島 聡,はまちや2,竹原
出版社:技術評論社  発売日:2016-04-23

Amazonで詳細を見る

Webアプリエンジニア養成読本[しくみ、開発、環境構築・運用…全体像を最新知識で最初から! ] (Software Design plus)

和田 裕介,石田 絢一 (uzulla),すがわら まさのり,斎藤 祐一郎
出版社:技術評論社  発売日:2014-04-15

Amazonで詳細を見る