LoginSignup
2
0

More than 3 years have passed since last update.

pythonで書くガチャ-基本的データ構造における実装-

Last updated at Posted at 2020-05-19

内容

ガチャのデータ設計(基本的構造1基本的構造2)に対応した実装を行います。

こちらのソースコードを改良

Pythonで書くガチャ-データ設計-

設定情報一覧

gacha_lottery

id gacha_group item_type times rarity omake_times omake_rarity cost
A_normal_1 A 0 1 0 0 0 10
A_normal_11 A 0 10 0 1 3 100
B_fighter_2 B 0 2 0 0 0 30
A_omake_2_11 A 0 9 2 2 3 200
A_omake_fighter_6 A 2 5 0 1 3 100

ガチャを実行する際に使用するIDという位置づけになりますので、具体的に分かりやすい文字列のIDにします

gacha_items

id gacha_group weight item_id
1 A 3 5101
2 A 9 4201
3 A 9 4301
4 A 9 4301
5 A 20 3201
6 A 20 3301
7 A 20 3401
8 A 40 2201
9 A 40 2301
10 A 40 2401
11 B 15 4201
12 B 30 3201
13 B 55 2201

items

id rarity item_name item_type HP
5101 5 UR_勇者 1 1200
4201 4 SSR_戦士 2 1000
4301 4 SSR_魔法使い 3 800
4401 4 SSR_神官 4 800
3201 3 SR_戦士 2 600
3301 3 SR_魔法使い 3 500
3401 3 SR_神官 4 500
2201 2 R_戦士 2 400
2301 2 R_魔法使い 3 300
2401 2 R_神官 4 300
3199 3 SR_勇者 1 600

rarity

id rarity_name
5 UR
4 SSR
3 SR
2 R
1 N

改修の観点

機能要件

基本的構造2のデータ構造で実現できるガチャの仕組みとして、以下の要件を満たすようにします。

  • ガチャ対象アイテムをグループ(母集団)として複数設定
  • ガチャ実行時に属性の絞り込み(item_type)
  • ガチャ実行時に通常抽選のレアリティ以上絞り込み(rarity)
  • ガチャ実行時に通常抽選の回数指定(times)
  • ガチャ実行時におまけのレアリティ以上絞り込み(omake_rarity)
  • ガチャ実行時におまけの抽選回数指定(omake_times)

データ構造の変更点

前回(Pythonで書くガチャ-データ設計-)の実装におけるデータ構造と大きく異なる点としてガチャ情報アイテム情報が分離されていることがあります。そのため、情報の紐付けが必要となります。

実装

gacha.py
import random

def gacha(lots, times: int=1) -> list:
    return random.choices(tuple(lots), weights=lots.values(), k=times)

def get_rarity_name(rarity: int) -> str:
    rarity_names = {5: "UR", 4: "SSR", 3: "SR", 2: "R", 1: "N"}
    return rarity_names[rarity]

# ガチャのIDと設定の辞書
def get_gacha_lottery_info(gacha_lottery_id: str) -> dict:
    gacha_lottery = {
        "A_normal_1":  {"gacha_group": "A", "item_type": 0, "times": 1, "rarity": 0, "omake_times": 0, "omake_rarity": 0, "cost":10},
        "A_normal_11":  {"gacha_group": "A", "item_type": 0, "times": 10, "rarity": 0, "omake_times": 1, "omake_rarity": 3, "cost":100},
        "B_fighter_2":  {"gacha_group": "B", "item_type": 0, "times": 2, "rarity": 0, "omake_times": 0, "omake_rarity": 0, "cost":30},
        "A_omake_2_11":  {"gacha_group": "A", "item_type": 0, "times": 9, "rarity": 2, "omake_times": 2, "omake_rarity": 3, "cost":200},
        "A_omake_fighter_6":  {"gacha_group": "A", "item_type": 2, "times": 5, "rarity": 0, "omake_times": 1, "omake_rarity": 3, "cost":100}
    }
    return gacha_lottery[gacha_lottery_id]

def set_gacha(items: dict, gacha_items: dict):
    # ガチャの設定に必要なアイテム情報をガチャアイテム情報に含める
    dic_gacha_items = {}
    for gacha_item_id, info in gacha_items.items():
        info["item_info"] = items[info["item_id"]]
        dic_gacha_items[gacha_item_id] = info

    # 抽選対象リストを抽出
    def get_lots(lottery_info: dict):
        lots = {}
        omake_lots = {}
        for id, info in dic_gacha_items.items():
            if lottery_info["gacha_group"] != info["gacha_group"]:
                continue
            if lottery_info["item_type"] and lottery_info["item_type"] != info["item_info"]["item_type"]:
                continue

            if not(lottery_info["rarity"]) or lottery_info["rarity"] <= info["item_info"]["rarity"]:
                lots[id] = info["weight"]

            if lottery_info["omake_times"]:
                if not(lottery_info["omake_rarity"]) or lottery_info["omake_rarity"] <= info["item_info"]["rarity"]:
                    omake_lots[id] = info["weight"]

        return lots, omake_lots

    # ガチャ実行
    def exec(gacha_lottery_id: str) -> list:
        lottery_info = get_gacha_lottery_info(gacha_lottery_id)
        lots, omake_lots =get_lots(lottery_info)
        ids = gacha(lots, lottery_info["times"])
        if len(omake_lots) > 0:
            ids.extend(gacha(omake_lots, lottery_info["omake_times"]))
        return ids

    return exec

def main():
    # アイテム情報
    items = {
        5101: {"rarity": 5, "item_name": "UR_勇者", "item_type": 1, "hp": 1200},
        4201: {"rarity": 4, "item_name": "SSR_戦士", "item_type": 2, "hp": 1000},
        4301: {"rarity": 4, "item_name": "SSR_魔法使い", "item_type": 3, "hp": 800},
        4401: {"rarity": 4, "item_name": "SSR_神官", "item_type": 4, "hp": 800},
        3201: {"rarity": 3, "item_name": "SR_戦士", "item_type": 2, "hp": 600},
        3301: {"rarity": 3, "item_name": "SR_魔法使い", "item_type": 3, "hp": 500},
        3401: {"rarity": 3, "item_name": "SR_神官", "item_type": 4, "hp": 500},
        2201: {"rarity": 2, "item_name": "R_戦士", "item_type": 2, "hp": 400},
        2301: {"rarity": 2, "item_name": "R_魔法使い", "item_type": 3, "hp": 300},
        2401: {"rarity": 2, "item_name": "R_神官", "item_type": 4, "hp": 300},
        3199: {"rarity": 3, "item_name": "SR_勇者", "item_type": 1, "hp": 600}
    }

    # ガチャアイテム情報
    gacha_items = {
        1:  {"gacha_group": "A", "weight": 3, "item_id": 5101},
        2:  {"gacha_group": "A", "weight": 9, "item_id": 4201},
        3:  {"gacha_group": "A", "weight": 9, "item_id": 4301},
        4:  {"gacha_group": "A", "weight": 9, "item_id": 4401},
        5:  {"gacha_group": "A", "weight": 20, "item_id": 3201},
        6:  {"gacha_group": "A", "weight": 20, "item_id": 3301},
        7:  {"gacha_group": "A", "weight": 20, "item_id": 3401},
        8:  {"gacha_group": "A", "weight": 40, "item_id": 2201},
        9:  {"gacha_group": "A", "weight": 40, "item_id": 2301},
        10: {"gacha_group": "A", "weight": 40, "item_id": 2401},
        11: {"gacha_group": "B", "weight": 15, "item_id": 4201},
        12: {"gacha_group": "B", "weight": 30, "item_id": 3201},
        13: {"gacha_group": "B", "weight": 55, "item_id": 2201}
    }

    # 実施するガチャのタプル
    gacha_lottery_ids = (
        "A_normal_1","A_normal_11","B_fighter_2","A_omake_2_11","A_omake_fighter_6"
    )

    #アイテム等をセット
    func_gacha = set_gacha(items, gacha_items)

    # gacha_lottery_idの設定にてガチャを実行
    for gacha_lottery_id in gacha_lottery_ids:
        print("==%s==" % gacha_lottery_id)
        ids = func_gacha(gacha_lottery_id)
        for id in ids:
            item_info = items[gacha_items[id]["item_id"]]
            print("ID:%d, %s, %s" % (id, get_rarity_name(item_info["rarity"]), item_info["item_name"]))


if __name__ == '__main__':
    main()

実行結果

gacha_lotteryのID分ガチャを実行して、挙動を確認します

==A_normal_1==
ID:10, R, R_神官
==A_normal_11==
ID:8, R, R_戦士
ID:10, R, R_神官
ID:10, R, R_神官
ID:6, SR, SR_魔法使い
ID:5, SR, SR_戦士
ID:8, R, R_戦士
ID:5, SR, SR_戦士
ID:8, R, R_戦士
ID:10, R, R_神官
ID:5, SR, SR_戦士
ID:7, SR, SR_神官
==B_fighter_2==
ID:13, R, R_戦士
ID:13, R, R_戦士
==A_omake_2_11==
ID:7, SR, SR_神官
ID:10, R, R_神官
ID:10, R, R_神官
ID:6, SR, SR_魔法使い
ID:8, R, R_戦士
ID:7, SR, SR_神官
ID:9, R, R_魔法使い
ID:9, R, R_魔法使い
ID:6, SR, SR_魔法使い
ID:4, SSR, SSR_神官
ID:1, UR, UR_勇者
==A_omake_fighter_6==
ID:8, R, R_戦士
ID:5, SR, SR_戦士
ID:5, SR, SR_戦士
ID:8, R, R_戦士
ID:5, SR, SR_戦士
ID:2, SSR, SSR_戦士

考察

機能要件は、満たすことができまました。
実際の運用に耐えうる仕組みとしては、不十分な箇所がありますが、
まずは、この構成が基本形となります。
今後は、運用上必要な機能要件を満たすために、新しい情報の追加、情報の再配置といったデータ構造の変更を行い、それに合わせて実装を改修していくことになります。

今回の実装において、改めて技術的に難しいことは特段導入していません。
端的に言えば、データ構造を再検討し実装を改修しただけです。
実際の開発現場に置いてサービス設計者には、企画者の原案(機能要件)を表現するために、文章の構成を見直しストーリーを書き直す脚本家のような能力が求められます。(大事なことなんですがイマイチ理解されていない現実があります

追記

情報量(マスタ)が多くなり、またユーザの状態も考慮したガチャを行うため、次回以降はDBを用いた実装を行います

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0