#内容
ガチャのデータ設計(基本的構造1、基本的構造2)に対応した実装を行います。
こちらのソースコードを改良
##設定情報一覧
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で書くガチャ-データ設計-)の実装におけるデータ構造と大きく異なる点としてガチャ情報とアイテム情報が分離されていることがあります。そのため、情報の紐付けが必要となります。
##実装
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を用いた実装を行います