3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

pythonで書くガチャ-実践編2・ステップアップガチャの基本-

Last updated at Posted at 2020-06-29

#内容
実際のゲームにおいては、色んな種類のガチャがありますが、
今回は、その中でステップアップガチャという種類のものを作成します。

以下のソースを改修して実装しています

pythonで書くガチャ-実践編1-

【2020-08-11更新】

  • ステップ数を記録することに変更しましたので、gacha_user_timesテーブルを、gacha_user_stepとテーブル名、カラム名を変更しています
  • 型ヒント、関数の内包表記などを変更しています

##ステップアップガチャとは?
ステップアップガチャとは、ユーザーが対象となるガチャの実行回数によって、抽選対象アイテムや、抽選の設定が変わっていくガチャになります。
ステップアップという名称の理由は、基本的には実行回数が多くなると、より良いアイテムが入手しやすくなるように設定されているからです。
以後、回数を重ねることをステップアップという言葉で表していきます。

##ステップアップガチャのデータ構造
まず、ステップアップした場合、何が変化するかを決めます。
今回は、前回のデータ構成を元にして、ガチャの引き方(gacha_lottery)の設定がステップアップするように改修を行うこととします。

考え方が分かりやすいように、段階(ステップ)を踏んで改修を行います

####改修のステップ1
まず、gacha_typeとしてstep_upという設定を追加します。
実行する

gacha

id start_time end_time gacha_group gacha_type
1 2020-05-01 00:00:00 2020-05-31 23:59:59 A normal
6 2020-06-01 00:00:00 2020-06-30 23:59:59 C normal
10 2020-05-01 00:00:00 2020-05-31 23:59:59 A step_up

gacha_lottery

id gacha_type item_type times rarity omake_times omake_rarity cost
normal_1 normal 0 1 0 0 0 10
normal_11 normal 0 10 0 1 3 100
step_up step_up 0 1 0 0 0 10

####改修のステップ2
gacha_lotteryステップ数毎の設定ができるように、step_noという項目を追加して設定を行います。

gacha_lottery

id gacha_type step_no item_type times rarity omake_times omake_rarity cost
normal_1 normal 0 1 0 0 0 10
normal_11 normal 0 10 0 1 3 100
step_up_1 step_up 1 0 1 0 0 0 10
step_up_2 step_up 2 0 2 0 1 3 30
step_up_3 step_up 3 0 3 0 2 3 50
step_up_4 step_up 4 0 10 0 1 5 100

####改修のステップ3
gacha_typeがnormalのガチャについては、step_noを0としてステップアップの設定が無いということにします。

gacha_lottery

id gacha_type step_no item_type times rarity omake_times omake_rarity cost
normal_1 normal 0 0 1 0 0 0 10
normal_11 normal 0 0 10 0 1 3 100
step_up_1 step_up 1 0 1 0 0 0 10
step_up_2 step_up 2 0 2 0 1 3 30
step_up_3 step_up 3 0 3 0 2 3 50
step_up_4 step_up 4 0 10 0 1 5 100

ステップ4まで実行した時点で、このステップアップガチャは終了となります(それ以上、実行できない)

図2.png

##ガチャ履歴の作成
ステップアップガチャは、ユーザの状態(ユーザのステップ状態)によってガチャの状態(確率など)が変化します。
つまり、あるユーザーが、その対象ガチャを実行したことによる変化したステップ状態を記録する必要があります。

そこで、ユーザ毎のガチャのステップ状態を記録できる場所(テーブル)と、記録をする処理を作ります。

####ユーザ情報(user)

id nick_name created_time
u001 taro 2020-05-31 23:59:59
u002 hana 2020-05-31 23:59:59

####ユーザガチャステップ情報(gacha_user_step)
| user_id | gacha_id | gacha_type | step_no | updated_time |
|:---:|:--------:|:------:|:------------:|:----:|:----:|
| u001 | 10 | step_up | 2 | 2020-05-11 22:00:00 |
| u002 | 10 | step_up | 3 | 2020-05-12 09:00:00 |

user_id+gacha_id+gacha_typeの複数カラムで一意となり、実行するたびにstep_noを+1(update)します。
なお、初回(1回目)の実行の場合は、レコードがありませんので初期ステップ(step_no=1)とみなして、実行時に次のステップ状態(step_no=2)として、レコードを作成(insert)します。

実装

実装上の仕様をまとめると以下の通りになります

  • ステップアップガチャの挙動の違い(normalガチャに対して)
  • gacha_user_stepステップの状態を記録する
  • 同じgacha_typeについて実行可能なステップのgacha_lotteryが抽出される
  • 実行する毎に、ステップが一つずつ進行していく
  • ステップ4まで実行した時点で、終了となる(それ以上、実行できない)

ソース上の変更箇所が分かるように以下のコメント記述しています

  • 【追加】:新しく追加した箇所
  • 【改修】:改修した箇所

###ガチャDB情報
構造・データの変更、履歴をリセットしたい場合は、この処理を再度実行してください

gacha_db.py
# -*- coding: utf-8 -*-
import sqlite3
import random

def get_items():
    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},
        4101: {"rarity": 4, "item_name": "SSR_勇者", "item_type": 1, "hp": 1000},
        5201: {"rarity": 5, "item_name": "UR_戦士", "item_type": 2, "hp": 1300},
        5301: {"rarity": 5, "item_name": "UR_魔法使い", "item_type": 3, "hp": 1000},
    }

    return convert_values(items)

def get_gacha_items():
    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},
        14: {"gacha_group": "C", "weight": 1, "item_id": 5101},
        15: {"gacha_group": "C", "weight": 1, "item_id": 5201},
        16: {"gacha_group": "C", "weight": 1, "item_id": 5301},
        17: {"gacha_group": "C", "weight": 9, "item_id": 4101},
        18: {"gacha_group": "C", "weight": 6, "item_id": 4201},
        19: {"gacha_group": "C", "weight": 6, "item_id": 4301},
        20: {"gacha_group": "C", "weight": 6, "item_id": 4401},
        21: {"gacha_group": "C", "weight": 20, "item_id": 3201},
        22: {"gacha_group": "C", "weight": 20, "item_id": 3301},
        23: {"gacha_group": "C", "weight": 20, "item_id": 3401},
        24: {"gacha_group": "C", "weight": 40, "item_id": 2201},
        25: {"gacha_group": "C", "weight": 40, "item_id": 2301},
        26: {"gacha_group": "C", "weight": 40, "item_id": 2401},
    }

    return convert_values(items)

def get_gacha():
    items = {
        1: {"start_time": "2020-05-01 00:00:00", "end_time": "2020-05-31 23:59:59", "gacha_group": "A",
            "gacha_type": "normal"},
        3: {"start_time": "2020-05-25 00:00:00", "end_time": "2020-05-31 23:59:59", "gacha_group": "B",
            "gacha_type": "fighter"},
        4: {"start_time": "2020-06-01 00:00:00", "end_time": "2020-06-04 23:59:59", "gacha_group": "C",
            "gacha_type": "omake_2"},
        5: {"start_time": "2020-05-20 00:00:00", "end_time": "2020-05-31 23:59:59", "gacha_group": "A",
            "gacha_type": "omake_fighter"},
        6: {"start_time": "2020-06-01 00:00:00", "end_time": "2020-06-30 23:59:59", "gacha_group": "C",
            "gacha_type": "normal"},
        10: {"start_time": "2020-05-01 00:00:00", "end_time": "2020-05-31 23:59:59", "gacha_group": "A",
            "gacha_type": "step_up"},
    }

    return convert_values(items)

def get_gacha_lottery():
    items = {
        "normal_1":  {"gacha_type": "normal", "step_no": 0, "item_type": 0, "times": 1, "rarity": 0, "omake_times": 0, "omake_rarity": 0, "cost":10},
        "normal_11":  {"gacha_type": "normal", "step_no": 0, "item_type": 0, "times": 10, "rarity": 0, "omake_times": 1, "omake_rarity": 3, "cost":100},
        "fighter":  {"gacha_type": "fighter", "step_no": 0, "item_type": 0, "times": 2, "rarity": 0, "omake_times": 0, "omake_rarity": 0, "cost":30},
        "omake_2":  {"gacha_type": "omake_2", "step_no": 0, "item_type": 0, "times": 9, "rarity": 2, "omake_times": 2, "omake_rarity": 3, "cost":150},
        "omake_fighter":  {"gacha_type": "omake_fighter", "step_no": 0, "item_type": 2, "times": 5, "rarity": 0, "omake_times": 1, "omake_rarity": 3, "cost":100},
        "step_up_1": {"gacha_type": "step_up", "step_no": 1, "item_type": 0, "times": 1, "rarity": 0, "omake_times": 0,
                     "omake_rarity": 0, "cost": 10},
        "step_up_2": {"gacha_type": "step_up", "step_no": 2, "item_type": 0, "times": 2, "rarity": 0, "omake_times": 1,
                      "omake_rarity": 3, "cost": 30},
        "step_up_3": {"gacha_type": "step_up", "step_no": 3, "item_type": 0, "times": 3, "rarity": 0, "omake_times": 2,
                      "omake_rarity": 3, "cost": 50},
        "step_up_4": {"gacha_type": "step_up", "step_no": 4, "item_type": 0, "times": 10, "rarity": 0, "omake_times": 1,
                      "omake_rarity": 5, "cost": 100},
    }

    return convert_values(items)

def get_users():
    items = {
        "u001": {"nick_name": "taro"},
        "u002": {"nick_name": "hana"},
    }

    return convert_values(items)


def convert_values(items: dict):
    values = []
    keys = []
    for id,info in items.items():
        if len(keys) == 0 :
            keys = list(info.keys())
            keys.insert(0,'id')

        value = list(info.values())
        value.insert(0,id)
        values.append(tuple(value))
    return keys,values

def print_rows(rows, keys: list):
    for row in rows:
        result = []
        for key in keys:
            result.append(str(row[key]))
        print(",".join(result))


def main():
    con = sqlite3.connect("data.db")
    con.row_factory = sqlite3.Row
    cursor = con.cursor()

    cursor.execute("DROP TABLE IF EXISTS gacha")

    cursor.execute(
        """CREATE TABLE gacha 
          (id INTEGER PRIMARY KEY AUTOINCREMENT
          ,start_time DATETIME
          ,end_time DATETIME
          ,gacha_group VARCHAR(32)
          ,gacha_type VARCHAR(32)
          )
        """
    )

    cursor.execute("DROP TABLE IF EXISTS gacha_items")
    cursor.execute(
        """CREATE TABLE gacha_items 
          (id INTEGER PRIMARY KEY AUTOINCREMENT
          ,gacha_group VARCHAR(32)
          ,weight INTEGER
          ,item_id INTEGER
          )
        """
    )

    # 改修
    cursor.execute("DROP TABLE IF EXISTS gacha_lottery")
    cursor.execute(
        """CREATE TABLE gacha_lottery 
          (id VARCHAR(32) PRIMARY KEY
          ,gacha_type VARCHAR(32)
          ,step_no INTEGER
          ,item_type INTEGER
          ,times INTEGER
          ,rarity INTEGER
          ,omake_times INTEGER
          ,omake_rarity INTEGER
          ,cost INTEGER
          )
        """
    )

    cursor.execute("DROP TABLE IF EXISTS items")
    cursor.execute(
        """CREATE TABLE items 
          (id INTEGER PRIMARY KEY AUTOINCREMENT
          ,rarity INTEGER
          ,item_name VARCHAR(64)
          ,item_type INTEGER
          ,hp INTEGER
          )
        """
    )

    # 追加
    cursor.execute("DROP TABLE IF EXISTS users")
    cursor.execute(
        """CREATE TABLE users 
          (id VARCHAR(16) PRIMARY KEY
          ,nick_name VARCHAR(64)
          )
        """
    )

    # 追加
    cursor.execute("DROP TABLE IF EXISTS gacha_user_step")
    cursor.execute(
        """CREATE TABLE gacha_user_step 
          (user_id VARCHAR(16) 
          ,gacha_id INTEGER
          ,gacha_type VARCHAR(32)
          ,step_no INTEGER DEFAULT 1
          ,updated_time DATETIME          
          ,PRIMARY KEY(user_id,gacha_id,gacha_type)
          )
        """
    )


    keys, values = get_items()
    sql = "insert into {0}({1}) values({2})".format('items', ','.join(keys), ','.join(['?'] * len(keys)))
    cursor.executemany(sql,values)
    select_sql = "SELECT * FROM items ORDER BY id"
    result = cursor.execute(select_sql)
    print("===items===")
    print_rows(result, keys)

    keys, values = get_gacha_items()
    sql = "insert into {0}({1}) values({2})".format('gacha_items', ','.join(keys), ','.join(['?'] * len(keys)))
    cursor.executemany(sql,values)
    select_sql = "SELECT * FROM gacha_items ORDER BY id"
    result = cursor.execute(select_sql)
    print("===gacha_items===")
    print_rows(result, keys)


    keys, values = get_gacha()
    sql = "insert into {0}({1}) values({2})".format('gacha', ','.join(keys), ','.join(['?'] * len(keys)))
    cursor.executemany(sql,values)
    select_sql = "SELECT * FROM gacha ORDER BY id"
    result = cursor.execute(select_sql)
    print("===gacha===")
    print_rows(result, keys)

    keys, values = get_gacha_lottery()
    sql = "insert into {0}({1}) values({2})".format('gacha_lottery', ','.join(keys), ','.join(['?'] * len(keys)))
    cursor.executemany(sql,values)
    select_sql = "SELECT * FROM gacha_lottery ORDER BY id"
    result = cursor.execute(select_sql)
    print("===gacha_lottery===")
    print_rows(result, keys)

    keys, values = get_users()
    sql = "insert into {0}({1}) values({2})".format('users', ','.join(keys), ','.join(['?'] * len(keys)))
    cursor.executemany(sql,values)
    select_sql = "SELECT * FROM users ORDER BY id"
    result = cursor.execute(select_sql)
    print("===users===")
    print_rows(result, keys)

    con.commit()
    con.close()

if __name__ == '__main__':
    main()

###ガチャ処理

gacha.py
import random
import sqlite3
from datetime import datetime
from typing import Dict, List, Tuple, Optional

def convert_row2dict(row) -> Dict:
    if row is None:
        return {}
    return {key: row[key] for key in row.keys()}

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]

# 実行可能ガチャ情報リスト
def get_gacha_list(cursor, now_time: int) -> Dict:
    select_sql = "SELECT * FROM gacha ORDER BY id"
    rows = cursor.execute(select_sql)
    results = {}
    for row in rows:
        start_time = int(datetime.strptime(row["start_time"], '%Y-%m-%d %H:%M:%S').timestamp())
        end_time = int(datetime.strptime(row["end_time"], '%Y-%m-%d %H:%M:%S').timestamp())
        # 日時の範囲で対象ガチャ情報を絞り込みます
        if start_time <= now_time <= end_time:
            results[row["id"]] = convert_row2dict(row)

    return results

# 【改修】
# 実行可能ガチャ情報リスト(gacha_lottery情報含む)
def get_available_gacha_info_list(cursor, now_time: int, user_id:str) -> Dict:
    gacha_list = get_gacha_list(cursor, now_time)
    for gacha_id, info in gacha_list.items():
        lottery_info_list = get_gacha_lottery_by_type(cursor, info["gacha_type"])
        gacha_user = get_gacha_user_step(cursor, user_id, gacha_id, info["gacha_type"])

        set_list = []
        for lottery_info in lottery_info_list:
            if lottery_info["step_no"] > 0:
                now_step_no = 1
                if len(gacha_user) > 0:
                    now_step_no = gacha_user["step_no"] + 1
                if now_step_no == lottery_info["step_no"]:
                    set_list.append(lottery_info)
            else:
                set_list.append(lottery_info)
        gacha_list[gacha_id]["gacha_lottery_list"] = set_list
    return gacha_list

def get_gacha(cursor, gacha_id: int, now_time: int) -> Dict:
    select_sql = "SELECT * FROM gacha WHERE id = ? ORDER BY id"
    cursor.execute(select_sql, (gacha_id,))
    row = cursor.fetchone()
    start_time = int(datetime.strptime(row["start_time"], '%Y-%m-%d %H:%M:%S').timestamp())
    end_time = int(datetime.strptime(row["end_time"], '%Y-%m-%d %H:%M:%S').timestamp())
    # 日時の範囲で対象ガチャ情報を絞り込みます
    if start_time <= now_time <= end_time:
        return convert_row2dict(row)

    return {}


def get_gacha_lottery(cursor, gacha_lottery_id: str) -> Dict:
    select_sql = "SELECT * FROM gacha_lottery WHERE id = ? ORDER BY id"
    cursor.execute(select_sql, (gacha_lottery_id,))
    return convert_row2dict(cursor.fetchone())


def get_gacha_lottery_by_type(cursor, gacha_type: str) -> List:
    select_sql = "SELECT * FROM gacha_lottery WHERE gacha_type = ? ORDER BY id"
    rows = cursor.execute(select_sql, (gacha_type,))
    return [
        convert_row2dict(row)
        for row in rows
    ]


def get_items_all(cursor) -> Dict:
    select_sql = "SELECT * FROM items ORDER BY id"
    rows = cursor.execute(select_sql)
    return {
        row["id"]: convert_row2dict(row)
        for row in rows
    }


def get_gacha_items(cursor, gacha_group: str) -> Dict:
    select_sql = "SELECT * FROM gacha_items WHERE gacha_group = ? ORDER BY id"
    rows = cursor.execute(select_sql, (gacha_group,))
    return {
        row["id"]: convert_row2dict(row)
        for row in rows
    }

def get_gacha_items_all(cursor) -> Dict:
    select_sql = "SELECT * FROM gacha_items ORDER BY id"
    rows = cursor.execute(select_sql)
    return {
        row["id"]: convert_row2dict(row)
        for row in rows
    }


# 【改修】
def get_gacha_info(cursor, gacha_id: int, gacha_lottery_id: str, now_time: int, user_id: str) -> Tuple[Optional[Dict], Optional[Dict]]:
    gacha = get_gacha(cursor, gacha_id, now_time)
    gacha_lottery = get_gacha_lottery(cursor, gacha_lottery_id)

    if len(gacha) == 0:
        print("===実行できないgacha_id:%s===" % (gacha_id))
        return None, None

    if len(gacha_lottery) == 0:
        print("===存在しないgacha_lottery_id:%s===" % (gacha_lottery_id))
        return None, None

    if gacha["gacha_type"] != gacha_lottery["gacha_type"]:
        print("===gacha_typeが異なる:%s,%s===" % (gacha["gacha_type"],gacha_lottery["gacha_type"]))
        return None, None

    # step_upガチャのチェック
    if gacha_lottery["step_no"] > 0:
        max_step_no = get_max_step_no(cursor, gacha["gacha_type"])
        gacha_user = get_gacha_user_step(cursor, user_id, gacha_id, gacha["gacha_type"])
        step_no = 1
        if len(gacha_user) > 0:
            step_no = gacha_user["step_no"]
        if max_step_no < step_no:
            print("===最大ステップまで実行済みです(最大ステップ:%s、実行しようとしているステップ:%s)===" %(max_step_no,gacha_lottery["step_no"]))
            return None, None

        if gacha_lottery["step_no"] != step_no:
            print("===実行できるステップと異なる(現在のステップ:%s、実行しようとしているステップ:%s)===" %(step_no, gacha_lottery["step_no"]))
            return None, None

    return gacha, gacha_lottery


# 【追加】
def get_gacha_user_step(cursor, user_id: str, gacha_id: int, gacha_type: str) -> Dict:
    select_sql = "SELECT * FROM gacha_user_step WHERE user_id = ? AND gacha_id = ? AND gacha_type = ?"
    cursor.execute(select_sql, (user_id,gacha_id,gacha_type))
    row = cursor.fetchone()

    return convert_row2dict(row)


# 【追加】
def get_max_step_no(cursor, gacha_type: str) -> Dict:
    select_sql = "SELECT MAX(step_no) AS max_step_no FROM gacha_lottery WHERE gacha_type = ?"
    cursor.execute(select_sql, (gacha_type,))
    row = cursor.fetchone()

    row_dict = convert_row2dict(row)
    return row_dict["max_step_no"]


# 【追加】
def update_gacha_user_step(cursor, user_id: str, gacha_id: int, gacha_type: str, update_time: int) -> None:
    row = get_gacha_user_step(cursor, user_id, gacha_id, gacha_type)
    dt = datetime.fromtimestamp(update_time)
    if len(row) == 0:
        keys = ("user_id","gacha_id","gacha_type","updated_time")
        sql = "insert into {0}({1}) values({2})".format('gacha_user_step', ','.join(keys), ','.join(['?'] * len(keys)))
        cursor.execute(sql, (user_id,gacha_id,gacha_type,dt))
    else:
        sql = "UPDATE gacha_user_step SET step_no = step_no + 1, updated_time = ? WHERE user_id = ? AND gacha_id = ? AND gacha_type = ?"
        cursor.execute(sql, (dt,user_id,gacha_id,gacha_type))


# 【改修】
def set_gacha(cursor, now_time: int, user_id:str):
    cursor = cursor
    now_time = now_time
    user_id = user_id
    items = get_items_all(cursor)

    # 抽選対象リストを抽出
    def get_lots(gacha_group: str, lottery_info: dict) -> Tuple[Dict,Dict]:
        gacha_items = get_gacha_items(cursor, gacha_group)
        dic_gacha_items = {}
        for gacha_item_id, gacha_item in gacha_items.items():
            gacha_item["item_info"] = items[gacha_item["item_id"]]
            dic_gacha_items[gacha_item_id] = gacha_item

        lots = {}
        omake_lots = {}
        for id, info in dic_gacha_items.items():
            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(exec_gacha_id: int, exec_gacha_lottery_id: str) -> List:
        gacha_info, gacha_lottery_info = get_gacha_info(cursor, exec_gacha_id, exec_gacha_lottery_id, now_time, user_id)
        if gacha_info is None or gacha_lottery_info is None:
            return []

        print("==%s==:gacha_group:%s" % (gacha_lottery_info["id"], gacha_info["gacha_group"]))
        lots, omake_lots = get_lots(gacha_info["gacha_group"], gacha_lottery_info)
        ids = gacha(lots, gacha_lottery_info["times"])
        if len(omake_lots) > 0:
            ids.extend(gacha(omake_lots, gacha_lottery_info["omake_times"]))

        # ステップアップガチャの場合は実行回数の更新を行う
        if len(ids) > 0 and gacha_lottery_info["step_no"] > 0:
            update_gacha_user_step(cursor, user_id, exec_gacha_id, gacha_info["gacha_type"], now_time)

        return ids

    return exec


def main():
    con = sqlite3.connect("data.db")
    con.row_factory = sqlite3.Row
    cursor = con.cursor()

    # 【追加】
    # 実際には、認証系の情報からユーザIDを取得することになります
    user_id = "u001"

    # 動作確認のため、ガチャ実行日時を指定
    now_time = int(datetime.strptime("2020-05-01 00:00:00", '%Y-%m-%d %H:%M:%S').timestamp())

    # 【追加】
    # 実行可能なgacha_lottery
    available_list = get_available_gacha_info_list(cursor,now_time,user_id)
    for gacha_id,available in available_list.items():
        print(gacha_id,available)

    # 初期化(実行日時、アイテム等をセット)
    func_gacha = set_gacha(cursor, now_time, user_id)


    items = get_items_all(cursor)
    gacha_items = get_gacha_items_all(cursor)
    # 単発ガチャを実行
    # gacha_idとgacha_lottery_idが実行時に渡される
    ids = func_gacha(1,"normal_1")
    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"]))

    # 11連ガチャを実行
    # gacha_idとgacha_lottery_idが実行時に渡される
    ids = func_gacha(1,"normal_11")
    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"]))

    # 【追加】
    # ステップアップガチャ
    for i in [1, 2, 3, 4, 5]:
        gacha_lottery_id = "step_up_{0}".format(str(i))
        ids = func_gacha(10,gacha_lottery_id)
        if len(ids) > 0:
            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"]))

    # 【追加】
    con.commit()


    con.close()


if __name__ == '__main__':
    main()

##実行結果
###1回目

1 {'id': 1, 'start_time': '2020-05-01 00:00:00', 'end_time': '2020-05-31 23:59:59', 'gacha_group': 'A', 'gacha_type': 'normal', 'gacha_lottery_list': [{'id': 'normal_1', 'gacha_type': 'normal', 'step_no': 0, 'item_type': 0, 'times': 1, 'rarity': 0, 'omake_times': 0, 'omake_rarity': 0, 'cost': 10}, {'id': 'normal_11', 'gacha_type': 'normal', 'step_no': 0, 'item_type': 0, 'times': 10, 'rarity': 0, 'omake_times': 1, 'omake_rarity': 3, 'cost': 100}]}
10 {'id': 10, 'start_time': '2020-05-01 00:00:00', 'end_time': '2020-05-31 23:59:59', 'gacha_group': 'A', 'gacha_type': 'step_up', 'gacha_lottery_list': [{'id': 'step_up_1', 'gacha_type': 'step_up', 'step_no': 1, 'item_type': 0, 'times': 1, 'rarity': 0, 'omake_times': 0, 'omake_rarity': 0, 'cost': 10}]}
==normal_1==:gacha_group:A
ID:9, R, R_魔法使い
==normal_11==:gacha_group:A
ID:1, UR, UR_勇者
ID:10, R, R_神官
ID:9, R, R_魔法使い
ID:5, SR, SR_戦士
ID:5, SR, SR_戦士
ID:8, R, R_戦士
ID:9, R, R_魔法使い
ID:10, R, R_神官
ID:10, R, R_神官
ID:9, R, R_魔法使い
ID:5, SR, SR_戦士
==step_up_1==:gacha_group:A
ID:2, SSR, SSR_戦士
==step_up_2==:gacha_group:A
ID:9, R, R_魔法使い
ID:4, SSR, SSR_神官
ID:3, SSR, SSR_魔法使い
==step_up_3==:gacha_group:A
ID:9, R, R_魔法使い
ID:2, SSR, SSR_戦士
ID:2, SSR, SSR_戦士
ID:3, SSR, SSR_魔法使い
ID:7, SR, SR_神官
==step_up_4==:gacha_group:A
ID:9, R, R_魔法使い
ID:10, R, R_神官
ID:6, SR, SR_魔法使い
ID:9, R, R_魔法使い
ID:2, SSR, SSR_戦士
ID:7, SR, SR_神官
ID:10, R, R_神官
ID:8, R, R_戦士
ID:10, R, R_神官
ID:7, SR, SR_神官
ID:1, UR, UR_勇者
===存在しないgacha_lottery_id:step_up_5===

###2回目

1 {'id': 1, 'start_time': '2020-05-01 00:00:00', 'end_time': '2020-05-31 23:59:59', 'gacha_group': 'A', 'gacha_type': 'normal', 'gacha_lottery_list': [{'id': 'normal_1', 'gacha_type': 'normal', 'step_no': 0, 'item_type': 0, 'times': 1, 'rarity': 0, 'omake_times': 0, 'omake_rarity': 0, 'cost': 10}, {'id': 'normal_11', 'gacha_type': 'normal', 'step_no': 0, 'item_type': 0, 'times': 10, 'rarity': 0, 'omake_times': 1, 'omake_rarity': 3, 'cost': 100}]}
10 {'id': 10, 'start_time': '2020-05-01 00:00:00', 'end_time': '2020-05-31 23:59:59', 'gacha_group': 'A', 'gacha_type': 'step_up', 'gacha_lottery_list': []}
==normal_1==:gacha_group:A
ID:7, SR, SR_神官
==normal_11==:gacha_group:A
ID:9, R, R_魔法使い
ID:4, SSR, SSR_神官
ID:2, SSR, SSR_戦士
ID:8, R, R_戦士
ID:8, R, R_戦士
ID:5, SR, SR_戦士
ID:10, R, R_神官
ID:7, SR, SR_神官
ID:10, R, R_神官
ID:7, SR, SR_神官
ID:5, SR, SR_戦士
===最大ステップまで実行済みです(最大ステップ:4、実行しようとしているステップ:1)===
===最大ステップまで実行済みです(最大ステップ:4、実行しようとしているステップ:2)===
===最大ステップまで実行済みです(最大ステップ:4、実行しようとしているステップ:3)===
===最大ステップまで実行済みです(最大ステップ:4、実行しようとしているステップ:4)===
===存在しないgacha_lottery_id:step_up_5===

##考察
1回目は、ステップ1〜ステップ4まで実行されています。また、ステップに応じてガチャの引き方が変化し、ステップアップしていることが分かります。
2回目は、1回目の実行結果が記録されているため(commit)、実行できずにステップアップガチャが終了していることが分かります。
実行時のステップ数の状態によって、それに応じたエラーメッセージを表示するようにしてありますので、呼び出し方を編集して処理を試していただければ、挙動の変化を実感できるかと思います。

今回作成したものは、ステップアップガチャの仕組みを理解することを目的として、基本的な処理の実装にとどまっており、複雑な制御は実装していません。
色々なゲームのステップアップガチャの挙動を観察し、どのような制御が足りないのかを考えてみると、ステップアップガチャを応用して出来ることが見えてくるかと思います。

履歴を初期化したい場合はgacha_db.pyを再度実行してください

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?