turnley.dev

PHP and MySQL code for faucet operators

Tools & Calculators

FCT-002

A weighted reward table you can actually afford

Pick a prize by weight, and compute the expected cost per claim before you launch instead of discovering it from your wallet balance.

Random rewards keep a faucet interesting, and they are also how faucets go broke. A jackpot tier with a weight that looks small can dominate the average payout, and nothing in the code will tell you — the balance just drains faster than expected.

The selection itself is a running total against one random draw. The part worth writing down is the second function: expected cost per claim, computed straight from the same table. Run it before you launch and you know what a thousand claims costs you.

Weights do not need to be percentages and do not need to total anything in particular. They are relative, which makes tuning one tier a local change rather than a rebalance of the whole table.

PHP
// [units => weight]
$table = [
    5      => 9000,   // common
    10     => 900,
    50     => 90,
    500    => 9,
    10000  => 1,      // jackpot
];

function pick_reward(array $table): int
{
    $total = array_sum($table);
    $roll = random_int(1, $total);          // not rand(): this decides money

    $acc = 0;
    foreach ($table as $units => $weight) {
        $acc += $weight;
        if ($roll <= $acc) {
            return (int) $units;
        }
    }
    return (int) array_key_first($table);   // unreachable, but never return null
}

function expected_cost(array $table): float
{
    $total = array_sum($table);
    $sum = 0.0;
    foreach ($table as $units => $weight) {
        $sum += $units * ($weight / $total);
    }
    return $sum;                            // average units paid per claim
}

// For the table above: total weight 10,000, expected_cost() === 7.3 units per claim.
// The 1-in-10,000 jackpot alone accounts for 1.0 of that — nearly 14% of the
// payout budget goes to a prize almost nobody sees. Check this before launch.

Using it

Print the expected cost next to the table in your admin panel. An operator editing weights should see the average payout change as they type.

Store the tier that was won on the claim row. Without it you cannot tell an unlucky week from a bug.

Cap the jackpot at something the faucet balance can survive on a bad day, not on an average one.

What bites people

Use random_int, not rand or mt_rand. Anything deciding a payout should come from a cryptographically secure source; the others are predictable from a few observed outputs.

Weights as floats invite rounding surprises at the boundaries. Keep them integers.

Scaling every tier down when the balance is low is kinder than shortening the cooldown, and it keeps the jackpot honest instead of quietly removing it.

This one touches real money. Point it at a throwaway wallet and watch a full cycle before you trust it with a live balance.

Also in Faucet Mechanics