Gacha written in python-Practice 1-

Contents

So far, we have only considered gacha processing, but in actual games

--Display the screen to execute the gacha --User select and execute gacha

Consider the data design and processing that can be handled in consideration of the above. Also, from this time, DB will be used to store data related to gacha.

** The following sources have been modified and implemented **

Gacha written in python-Addition of period setting function-

Collect redundant setting information

Gacha information

id start_time end_time gacha_group gacha_lottery_id
1 2020-05-01 00:00:00 2020-05-31 23:59:59 A normal_1
2 2020-05-01 00:00:00 2020-05-31 23:59:59 A normal_11
6 2020-06-01 00:00:00 2020-06-30 23:59:59 C normal_1
7 2020-06-01 00:00:00 2020-06-30 23:59:59 C normal_11

Elements of gacha information

The role of ** Gacha Information ** is to set a group of target gacha items in the implementation period and link it to the how to draw the gacha. The only difference between the IDs ** 1 ** and ** 2 ** (or ** 6 ** and ** 7 **) is that the how to draw a gacha is one-shot (once) or 11 consecutive. is. Setting redundant information increases time and effort and leads to setting mistakes during operation. If possible, I would like to avoid setting the same information more than once, so I will modify the data structure.

Change of gacha information data structure

From the ** gacha ** information, add an item called gacha_type instead of gacha_lottery_id. Add gacha_type to ** gacha_lottery ** to link gacha information and gacha method definition information.

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

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

Imagine that the following is displayed on the actual in-game gacha screen.

gacha_2.png

Implementation

Since various information will be acquired from the DB from this implementation, first create a table and set the data.

Gacha DB information (executed if there is a change in structure or data)

gacha_db


# -*- coding: utf-8 -*-
import sqlite3
import random

def get_items():
    items = {
        5101: {"rarity": 5, "item_name": "UR_Brave", "item_type": 1, "hp": 1200},
        4201: {"rarity": 4, "item_name": "SSR_Warrior", "item_type": 2, "hp": 1000},
        4301: {"rarity": 4, "item_name": "SSR_Wizard", "item_type": 3, "hp": 800},
        4401: {"rarity": 4, "item_name": "SSR_Priest", "item_type": 4, "hp": 800},
        3201: {"rarity": 3, "item_name": "SR_Warrior", "item_type": 2, "hp": 600},
        3301: {"rarity": 3, "item_name": "SR_Wizard", "item_type": 3, "hp": 500},
        3401: {"rarity": 3, "item_name": "SR_Priest", "item_type": 4, "hp": 500},
        2201: {"rarity": 2, "item_name": "R_Warrior", "item_type": 2, "hp": 400},
        2301: {"rarity": 2, "item_name": "R_Wizard", "item_type": 3, "hp": 300},
        2401: {"rarity": 2, "item_name": "R_Priest", "item_type": 4, "hp": 300},
        3199: {"rarity": 3, "item_name": "SR_Brave", "item_type": 1, "hp": 600},
        #Added below
        4101: {"rarity": 4, "item_name": "SSR_Brave", "item_type": 1, "hp": 1000},
        5201: {"rarity": 5, "item_name": "UR_Warrior", "item_type": 2, "hp": 1300},
        5301: {"rarity": 5, "item_name": "UR_Wizard", "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},
        #Added below
        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"},
    }

    return convert_values(items)

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

    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()

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

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

    #         1:  {"gacha_group": "A", "weight": 3, "item_id": 5101},

    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
          )
        """
    )

    #  "normal_1":  {"item_type": 0, "times": 1, "rarity": 0, "omake_times": 0, "omake_rarity": 0, "cost":10},

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

    #        5101: {"rarity": 5, "item_name": "UR_Brave", "item_type": 1, "hp": 1200},
    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
          )
        """
    )

    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)


    con.commit()
    con.close()

if __name__ == '__main__':
    main()

Gacha processing

gacha.py


import random
import sqlite3
from datetime import datetime

def convert_row2dict(row) -> dict:
    keys = row.keys()
    return {key: row[key] for key in 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]

#Executable gacha information list
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())
        #Narrow down the target gacha information within the range of the date and time
        if start_time <= now_time <= end_time:
            results[row["id"]] = convert_row2dict(row)

    return results

#Executable gacha information list (gacha_(Including lottery information)
def get_available_gacha_info_list(cursor, now_time: int) -> 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_list[gacha_id]["gacha_lottery_list"] = lottery_info_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())
    #Narrow down the target gacha information within the range of the date and time
    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,))
    row = cursor.fetchone()
    return convert_row2dict(row)


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,))
    results = []
    for row in rows:
        row_dict = convert_row2dict(row)
        results.append(row_dict)
    return results


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


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,))
    results = {}
    for row in rows:
        row_dict = convert_row2dict(row)
        results[row["id"]] = row_dict
    return results


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



def get_gacha_info(cursor, gacha_id: int, gacha_lottery_id: str, now_time: int):
    gacha = get_gacha(cursor, gacha_id, now_time)
    gacha_lottery = get_gacha_lottery(cursor, gacha_lottery_id)

    if gacha["gacha_type"] != gacha_lottery["gacha_type"]:
        return None, None

    return gacha, gacha_lottery


def set_gacha(cursor, now_time: int):
    cursor = cursor
    now_time = now_time
    items = get_items_all(cursor)

    #Extract the lottery target list
    def get_lots(gacha_group: str, lottery_info: 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

    #Gacha execution
    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)

        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"]))

        return ids

    return exec


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


    #Specify the gacha execution date and time to check the operation
    now_time = int(datetime.strptime("2020-05-01 00:00:00", '%Y-%m-%d %H:%M:%S').timestamp())

    #Initialization (set execution date and time, items, etc.)
    func_gacha = set_gacha(cursor, now_time)

    items = get_items_all(cursor)
    gacha_items = get_gacha_items_all(cursor)
    #Execute one-shot gacha
    # gacha_id and gacha_lottery_id passed at runtime
    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"]))

    #Execute 11 consecutive gachas
    # gacha_id and gacha_lottery_id passed at runtime
    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"]))

    #con.commit()
    con.close()


if __name__ == '__main__':
    main()

Execution result

==normal_1==:gacha_group:A
ID:8, R, R_Warrior
==normal_11==:gacha_group:A
ID:5, SR, SR_Warrior
ID:8, R, R_Warrior
ID:3, SSR, SSR_Wizard
ID:10, R, R_Priest
ID:9, R, R_Wizard
ID:9, R, R_Wizard
ID:8, R, R_Warrior
ID:1, UR, UR_Brave
ID:10, R, R_Priest
ID:10, R, R_Priest
ID:7, SR, SR_Priest

Supplement

You may be wondering if you are passing ** gacha_id ** and ** gacha_lottery_id ** as arguments when you run the gacha. All you need to execute a gacha is ** gacha_lottery_id **, and if you get the ** gacha_id ** that corresponds to the date and time when the process is performed, there is no problem with the gacha process. However, if the gacha is executed at the switching timing of the gacha execution period, the user may execute the gacha of the target item different from the target item that he / she wants to draw.

Example

In order to avoid such a thing, ** gacha_id ** is also passed as an argument when executing the gacha, and if that ** gacha_id ** is not covered by the implementation period, the gacha has ended and the user is informed. Notify and display the gacha screen again. Since gacha is a process that assumes that billing is involved, it is necessary to prevent processing that is strictly different from the user's intention.

In this source, error handling is omitted a little, so please consider and implement the error handling required for the application.

Recommended Posts

Gacha written in python-Practice 1-
Gacha written in python-Practice 2 ・ Basics of step-up gacha-
Gacha written in Python-Data design-
Gacha written in python-Practice 3 ・ Addition of step-up gacha functions-
Gacha written in Python -BOX gacha-
Simple gacha logic written in Python
Gacha written in python-Rarity confirmed with bonus-
Gacha written in python-Implementation in basic data structure-
Gacha written in python-Addition of period setting function-
Squid Lisp written in Python: Hy
Compatibility diagnosis program written in python
Fourier series verification code written in Python
Stress Test with Locust written in Python
Markov chain transition probability written in Python