Posts

Card distribution across splinterlands visualized

6 comments·0 reblogs
beaker007
74
0 views
·
min-read

Card distrubtion.webp

Introduction

Understanding the Splinterlands Card Distribution

In this post, we’ll delve into the current distribution of Splinterlands cards, focusing on their classification by rarity and edition. We’ll explore both regular and gold foil cards, offering a comprehensive analysis through a series of visual graphs. These insights will help you understand the market dynamics of Splinterlands cards, including how they’re distributed and how many have been burned. This analysis is crucial for collectors, players, and investors alike, providing a detailed view of the card landscape in Splinterlands.
Image from thread

Total Card Distribution

Overview of Total Card Distribution

Before diving into the specifics of regular and gold foil distributions, it’s important to first get a bird’s-eye view of the overall card distribution in Splinterlands. The chart below showcases the total distribution of cards across all rarities and editions. This high-level view allows you to understand the general spread of cards in the game.

Total Card Distribution Grouped by Rarity and Edition

To get a clearer picture of the less commonly distributed editions, we’ve also included a zoomed-in chart. This focuses on the editions with fewer cards in circulation, providing a more detailed look at their distribution.

Zoomed-In View of Lesser Distributed Editions

Specially for @davemccoy last minute update. Added the soulbound unlock disctibution

Total soulbound unbounded

In the end i created a small overview where all the soulbound unbound % is displayed.
Currently the Kulu Mastermind is the most unbounded soulbound with 1.4%

 Table with unbound percentage

Image from thread

Regular Foil Distribution

Regular Foil Cards by Rarity and Edition

In this section, we’ll take a closer look at the total distribution of regular foil cards. The chart below categorizes these cards by rarity (Common, Rare, Epic, and Legendary) and edition (Alpha, Beta, Promo, Reward, and Chaos Legion, etc.). This breakdown gives you an insight into which cards are most prevalent and which editions hold the most value in terms of rarity.

Regular Foil Total Distribution Grouped by Rarity and Edition

Regular Foil Total Distribution of epic and rare

Image from thread

Gold Foil Distribution

Gold Foil Cards by Rarity and Edition

Gold foil cards are often seen as the crown jewels of Splinterlands, highly sought after due to their rarity and visual appeal and their bonusses in game. This chapter presents the distribution of gold foil cards, again categorized by rarity and edition. The graph here will show how these cards are spread across different editions and rarity levels, offering a clear picture of their availability in the game.

Gold Foil Total Distribution Grouped by Rarity and Edition

Gold Foil Total Distribution of epic and rare
Image from thread

Regular Foil Burned

Burned Regular Foil Cards by Rarity and Edition

Card burning is a crucial mechanism in Splinterlands, affecting the supply and value of cards. This section focuses on regular foil cards that have been burned, grouped by rarity and edition. The accompanying chart will illustrate how many of these cards have been removed from circulation, providing insights into how burning impacts the overall card distribution.

Regular Foil Cards Burned Grouped by Rarity and Edition
Image from thread

Gold Foil Burned

Burned Gold Foil Cards by Rarity and Edition

Continuing from the previous section, we now shift our focus to gold foil cards that have been burned. This chart will show the distribution of burned gold foil cards across different rarities and editions, helping you understand which high-value cards have been permanently removed from the game.

Gold Foil Cards Burned Grouped by Rarity and Edition

Image from thread

Burned Percentage

Percentage of Cards Burned Relative to Total Distribution

This chapter ties everything together by presenting a chart that represents the percentage of burned cards against the total distribution. Grouped by rarity and edition, this analysis highlights the impact of burning on each card type. Pay special attention to the Promo edition, where the burn rate of rare cards is notably high, signaling their increasing scarcity and potential value.

Percentage of Cards Burned vs. Total Distribution Grouped by Rarity and Edition
Image from thread

Python Code

Reproducing the Charts with Python

For those who want to delve deeper and create their own visualizations, this section provides the Python code used to generate the charts in this blog post. The code allows you to recreate the charts and even make them interactive. To get started, make sure you have the following Python packages installed:

pip install pandas requests plotly dash urllib3 
import dash 
import pandas as pd 
import plotly.graph_objects as go 
import requests 
from dash import dcc, html, dash_table 
from requests.adapters import HTTPAdapter 
from urllib3 import Retry 
 
# Define URLs 
base_url = 'https://api2.splinterlands.com/' 
 
# Set up retry strategy 
retry_strategy = Retry( 
    total=10, 
    status_forcelist=[429, 500, 502, 503, 504], 
    backoff_factor=2, 
    allowed_methods=['HEAD', 'GET', 'OPTIONS'] 
) 
adapter = HTTPAdapter(max_retries=retry_strategy) 
http = requests.Session() 
http.mount('https://', adapter) 
 
 
def get_card_details(): 
    address = base_url + 'cards/get_details' 
    return pd.DataFrame(http.get(address).json()).set_index('id') 
 
 
# Edition and Rarity mapping enums 
class Edition: 
    alpha = 0 
    beta = 1 
    promo = 2 
    reward = 3 
    untamed = 4 
    dice = 5 
    gladius = 6 
    chaos = 7 
    rift = 8 
    soulbound = 10 
    rebellion = 12 
    soulboundrb = 13 
 
 
class Rarity: 
    common = 1 
    rare = 2 
    epic = 3 
    legendary = 4 
 
 
# Assume spl is a module with get_card_details() function to fetch card data. 
data = get_card_details() 
 
edition_mapping = { 
    Edition.alpha: 'Alpha', 
    Edition.beta: 'Beta', 
    Edition.promo: 'Promo', 
    Edition.reward: 'Reward', 
    Edition.untamed: 'Untamed', 
    Edition.dice: 'Dice', 
    Edition.gladius: 'Gladius', 
    Edition.chaos: 'Chaos', 
    Edition.rift: 'Rift', 
    Edition.soulbound: 'Soulbound', 
    Edition.rebellion: 'Rebellion', 
    Edition.soulboundrb: 'Soulbound Rebellion' 
} 
 
rarity_mapping = { 
    Rarity.common: 'Common', 
    Rarity.rare: 'Rare', 
    Rarity.epic: 'Epic', 
    Rarity.legendary: 'Legendary' 
} 
 
# Prepare data by aggregating across all card distributions 
all_distributions = [] 
for card_id, distributions in data['distribution'].items(): 
    for dist in distributions: 
        all_distributions.append(dist) 
 
df = pd.DataFrame(all_distributions) 
df['num_cards'] = df['num_cards'].astype(int) 
df['num_burned'] = df['num_burned'].astype(int) 
df['unbound_cards'] = df['unbound_cards'].astype(int) 
 
# Create a mapping from card_detail_id to rarity 
rarity_mapping_df = {card_detail_id: rarity for card_detail_id, rarity in data['rarity'].items()} 
 
# Add rarity column to the DataFrame 
df['rarity'] = df['card_detail_id'].map(rarity_mapping_df) 
df['rarity'] = df['rarity'].map(rarity_mapping)  # Map to human-readable rarity names 
 
# Map edition to human-readable names 
df['edition_num'] = df['edition']  # Preserve edition number for sorting 
df['edition'] = df['edition'].map(edition_mapping) 
 
# Ensure the edition is ordered by edition number 
edition_order = [edition_mapping[num] for num in sorted(edition_mapping)] 
 
# Sort by edition number and rarity 
rarity_order = ['Common', 'Rare', 'Epic', 'Legendary'] 
df['rarity'] = pd.Categorical(df['rarity'], categories=rarity_order, ordered=True) 
df = df.sort_values(by=['edition_num', 'rarity']) 
 
# Filter data for non-gold and gold cards 
df_non_gold = df[df['gold'] == False] 
df_gold = df[df['gold'] == True] 
 
# Group by edition and rarity and calculate totals 
grouped_non_gold_df = df_non_gold.groupby( 
    ['edition_num', 'edition', 'rarity'], 
    observed=True, 
).agg({ 
    'num_cards': 'sum', 
    'num_burned': 'sum', 
}).reset_index() 
 
grouped_gold_df = df_gold.groupby( 
    ['edition_num', 'edition', 'rarity'], 
    observed=True, 
).agg({ 
    'num_cards': 'sum', 
    'num_burned': 'sum' 
}).reset_index() 
 
# Combine non-gold and gold data for total calculations 
combined_df = df.groupby( 
    ['edition_num', 'edition', 'rarity'], 
    observed=True, 
).agg({ 
    'num_cards': 'sum', 
    'num_burned': 'sum' 
}).reset_index() 
 
# Group by edition to get total distribution for each set 
total_distribution_df = df.groupby(['edition_num', 'edition']).agg({ 
    'num_cards': 'sum' 
}).reset_index() 
 
# Calculate percentage burned 
combined_df['percent_burned'] = (combined_df['num_burned'] / combined_df['num_cards']) * 100 
 
# Define colors for each rarity 
rarity_colors = { 
    'Common': 'gray', 
    'Rare': 'blue', 
    'Epic': 'purple', 
    'Legendary': 'orange' 
} 
 
# Calculate the percentage of distributed cards vs unbound for Soulbound edition 
soulbound_df = df[df['edition'] == 'Soulbound'].copy() 
soulbound_df['percent_unbound'] = ((soulbound_df['unbound_cards'] / soulbound_df['num_cards']) * 100).round(2) 
soulbound_df.loc[:, 'card_name'] = soulbound_df.apply(lambda row: data.loc[row['card_detail_id']]['name'], axis=1) 
 
# Define the base URL 
image_base_url = "https://images.hive.blog/125x0/https://d36mxiodymuqjm.cloudfront.net/cards_by_level/soulbound/" 
 
# Mapping rarity to level 
rarity_to_level = { 
    'Common': '10', 
    'Rare': '8', 
    'Epic': '6', 
    'Legendary': '4' 
} 
 
 
# Function to generate image URL 
def generate_image_url(row): 
    card_name = row['card_name'].replace(' ', '%20')  # Replace spaces with %20 
    level = rarity_to_level[row['rarity']] 
    markdown_prefix = "![" + str(card_name) + "]" 
 
    if row['gold']: 
        return f"![{markdown_prefix}]({image_base_url}{card_name}_lv{level}_gold.png)" 
    else: 
        return f"![{markdown_prefix}]({image_base_url}{card_name}_lv{level}.png)" 
 
 
# Apply the function to generate the image URLs 
soulbound_df['image_url'] = soulbound_df.apply(generate_image_url, axis=1) 
 
# Select only necessary columns for the table 
soulbound_table_df = soulbound_df[['image_url', 'rarity', 'percent_unbound', 'num_cards', 'unbound_cards']] 
 
# Create a Dash app 
app = dash.Dash(__name__) 
 
# Create the layout for the Dash app 
app.layout = html.Div( 
    style={'backgroundColor': '#1a1a1a', 'color': '#FFFFFF'},  # Dark theme styles 
    children=[ 
        html.H1("Distribution and Burned Totals by Edition and Rarity", style={'textAlign': 'center'}), 
 
        dcc.Graph( 
            id='total-distribution-chart', 
            figure={ 
                'data': [ 
                            go.Bar( 
                                x=[edition],  # Single edition per bar 
                                y=[total_distribution_df[total_distribution_df['edition'] == edition][ 
                                       'num_cards'].sum()], 
                                name=edition, 
                                # marker=dict(color=color)  # Assign different colors for each edition 
                            ) 
                            for edition in edition_order 
                        ] + [ 
                            go.Bar( 
                                x=['Soulbound Unbound'],  # Only for Soulbound 
                                y=[df[df['edition'] == 'Soulbound']['unbound_cards'].sum()], 
                                # Total unbound cards for Soulbound 
                                name='Soulbound (Unbound Cards)', 
                                marker=dict(color='green')  # Different color for distinction 
                            ) 
                        ], 
                'layout': go.Layout( 
                    title='Total Distribution by Edition', 
                    title_font=dict(color='#FFFFFF'), 
                    xaxis={'title': 'Edition', 'color': '#FFFFFF', 'categoryorder': 'array', 
                           'categoryarray': edition_order + ['Soulbound']}, 
                    yaxis={'title': 'Total Distribution', 'color': '#FFFFFF'}, 
                    paper_bgcolor='#1a1a1a', 
                    plot_bgcolor='#1a1a1a', 
                    font=dict(color='#FFFFFF'), 
                    showlegend=True  # Ensure the legend is displayed 
                ) 
            } 
        ), 
 
        # Non-Gold Charts 
        html.H2("Regular Foil Cards", style={'textAlign': 'center'}), 
 
        # Chart 1: Non-Gold Total Distribution 
        dcc.Graph( 
            id='non-gold-distribution-chart', 
            figure={ 
                'data': [ 
                    go.Bar( 
                        x=grouped_non_gold_df[grouped_non_gold_df['rarity'] == rarity]['edition'], 
                        y=grouped_non_gold_df[grouped_non_gold_df['rarity'] == rarity]['num_cards'], 
                        name=f'{rarity} Distribution', 
                        marker=dict(color=rarity_colors[rarity]) 
                    ) 
                    for rarity in rarity_order  # Use ordered rarity list 
                ], 
                'layout': go.Layout( 
                    title='Total Distribution by Edition and Rarity (Regular Foil)', 
                    title_font=dict(color='#FFFFFF'), 
                    xaxis={'title': 'Edition', 'color': '#FFFFFF', 'categoryorder': 'array', 
                           'categoryarray': edition_order}, 
                    yaxis={'title': 'Total Distribution', 'color': '#FFFFFF'}, 
                    barmode='group', 
                    paper_bgcolor='#1a1a1a', 
                    plot_bgcolor='#1a1a1a', 
                    font=dict(color='#FFFFFF') 
                ) 
            } 
        ), 
 
        # Chart 2: Non-Gold Burned Totals 
        dcc.Graph( 
            id='non-gold-burned-chart', 
            figure={ 
                'data': [ 
                    go.Bar( 
                        x=grouped_non_gold_df[grouped_non_gold_df['rarity'] == rarity]['edition'], 
                        y=grouped_non_gold_df[grouped_non_gold_df['rarity'] == rarity]['num_burned'], 
                        name=f'{rarity} Burned', 
                        marker=dict(color=rarity_colors[rarity]) 
                    ) 
                    for rarity in rarity_order  # Use ordered rarity list 
                ], 
                'layout': go.Layout( 
                    title='Total Burned by Edition and Rarity (Regular Foil)', 
                    title_font=dict(color='#FFFFFF'), 
                    xaxis={'title': 'Edition', 'color': '#FFFFFF', 'categoryorder': 'array', 
                           'categoryarray': edition_order}, 
                    yaxis={'title': 'Total Burned', 'color': '#FFFFFF'}, 
                    barmode='group', 
                    paper_bgcolor='#1a1a1a', 
                    plot_bgcolor='#1a1a1a', 
                    font=dict(color='#FFFFFF') 
                ) 
            } 
        ), 
 
        # Gold Charts 
        html.H2("Gold Foil Cards", style={'textAlign': 'center'}), 
 
        # Chart 3: Gold Total Distribution 
        dcc.Graph( 
            id='gold-distribution-chart', 
            figure={ 
                'data': [ 
                    go.Bar( 
                        x=grouped_gold_df[grouped_gold_df['rarity'] == rarity]['edition'], 
                        y=grouped_gold_df[grouped_gold_df['rarity'] == rarity]['num_cards'], 
                        name=f'{rarity} Distribution', 
                        marker=dict(color=rarity_colors[rarity]) 
                    ) 
                    for rarity in rarity_order  # Use ordered rarity list 
                ], 
                'layout': go.Layout( 
                    title='Total Distribution by Edition and Rarity (Gold Foil)', 
                    title_font=dict(color='#FFFFFF'), 
                    xaxis={'title': 'Edition', 'color': '#FFFFFF', 'categoryorder': 'array', 
                           'categoryarray': edition_order}, 
                    yaxis={'title': 'Total Distribution', 'color': '#FFFFFF'}, 
                    barmode='group', 
                    paper_bgcolor='#1a1a1a', 
                    plot_bgcolor='#1a1a1a', 
                    font=dict(color='#FFFFFF') 
                ) 
            } 
        ), 
 
        # Chart 4: Gold Burned Totals 
        dcc.Graph( 
            id='gold-burned-chart', 
            figure={ 
                'data': [ 
                    go.Bar( 
                        x=grouped_gold_df[grouped_gold_df['rarity'] == rarity]['edition'], 
                        y=grouped_gold_df[grouped_gold_df['rarity'] == rarity]['num_burned'], 
                        name=f'{rarity} Burned', 
                        marker=dict(color=rarity_colors[rarity]) 
                    ) 
                    for rarity in rarity_order  # Use ordered rarity list 
                ], 
                'layout': go.Layout( 
                    title='Total Burned by Edition and Rarity (Gold Foil)', 
                    title_font=dict(color='#FFFFFF'), 
                    xaxis={'title': 'Edition', 'color': '#FFFFFF', 'categoryorder': 'array', 
                           'categoryarray': edition_order}, 
                    yaxis={'title': 'Total Burned', 'color': '#FFFFFF'}, 
                    barmode='group', 
                    paper_bgcolor='#1a1a1a', 
                    plot_bgcolor='#1a1a1a', 
                    font=dict(color='#FFFFFF') 
                ) 
            } 
        ), 
 
        # Combined Chart: Percentage Burned Only 
        html.H2("Percentage Burned by Edition and Rarity", style={'textAlign': 'center'}), 
        dcc.Graph( 
            id='percentage-burned-chart', 
            figure={ 
                'data': [ 
                    go.Bar( 
                        x=combined_df[combined_df['rarity'] == rarity]['edition'], 
                        y=combined_df[combined_df['rarity'] == rarity]['percent_burned'], 
                        name=f'{rarity} % Burned', 
                        marker=dict(color=rarity_colors[rarity]) 
                    ) 
                    for rarity in rarity_order 
                ], 
                'layout': go.Layout( 
                    title='Percentage Burned by Edition and Rarity', 
                    title_font=dict(color='#FFFFFF'), 
                    xaxis={'title': 'Edition', 'color': '#FFFFFF', 'categoryorder': 'array', 
                           'categoryarray': edition_order}, 
                    yaxis={'title': 'Percentage Burned (%)', 'color': '#FFFFFF'}, 
                    barmode='group', 
                    paper_bgcolor='#1a1a1a', 
                    plot_bgcolor='#1a1a1a', 
                    font=dict(color='#FFFFFF') 
                ) 
            } 
        ), 
 
        # New Table for Soulbound Edition 
        html.H2("Soulbound Edition - Distributed vs Unbound Percentage", style={'textAlign': 'center'}), 
 
        dash_table.DataTable( 
            id='soulbound-table', 
            columns=[ 
                {'name': 'Image', 'id': 'image_url', 'presentation': 'markdown'},  # Markdown column for images 
                {'name': 'Rarity', 'id': 'rarity'}, 
                {'name': 'Percentage Unbound (%)', 'id': 'percent_unbound'}, 
                {'name': 'Number of Cards', 'id': 'num_cards'}, 
                {'name': 'Unbound Cards', 'id': 'unbound_cards'}, 
            ], 
            data=soulbound_table_df.to_dict('records'), 
            style_header={'backgroundColor': '#333333', 'fontWeight': 'bold'}, 
            row_selectable=False, 
            row_deletable=False, 
            editable=False, 
            filter_action='native', 
            sort_action='native', 
            markdown_options={"link_target": "_blank"}, 
            style_table={'width': '40%', 'margin': 'auto', 'align': 'left'}, 
            style_cell={ 
                'backgroundColor': '#1a1a1a', 
                'color': '#FFFFFF', 
                'minWidth': 'auto', 'width': '20%', 'maxWidth': 'auto', 
                'padding': '5px', 
                'textAlign': 'center' 
            }, 
        ) 
 
    ] 
) 
 
if __name__ == '__main__': 
    app.run_server(debug=True) 
 

Example

Example usage when you execute the code and navigate to: http://localhost:8050/

Total distrubution gif

regular foil gif

Table percentage unbounded gif

Image from thread

That's all for this week hope you enjoyed reading this. See you all on the battlefield.

Image from thread

Do you also want to be part of this amazing play to earn game consider using my referral link.