Составления алгоритма выпадения предметов

Рейтинг: 0Ответов: 2Опубликовано: 13.05.2023

Нужно составить алгоритм выпадения предметов.

Входные данные:

open_cost - Стоимость открытия.

items - Массив предметов которые могут выпасть.

item['cost'] - Стоимость предмета по отдельности.

Также можно использовать:

items_count - Количество всех предметов.

items_cost - Стоимость всех предметов.

И т.п.

Алгоритм должен выдавать в среднем предмет со стоимостью равной стоимости открытия.


Мне нужен алгоритм на php. Вы можете писать на любом удобном языке, мне главное понять принцип.

Так же у меня есть свой алгоритм, но его проблема в том что для разных предметов требуется разный коэффициент, и его приходить находить с помощью подбора.

Вот данный алгоритм:

function getClosest($search, $array)
{
  $num = null;
  foreach ($array as $item) {
    if ($item['chance'] < $search) {
      continue;
    }
    if ($num === null || $item['chance'] < $num['chance']) {
      $num = $item;
    }
  }
  return $num;
}

function getRandomSkin($items, $open_cost, $coeff)
{

  $accum = 0;
  foreach ($items as &$item) {
    if ($open_cost >= $item['cost']) {
      $chance = $item['cost'] / $open_cost * $coeff;
    } else {
      $chance = $open_cost / $item['cost'];
    }

    $item['chance'] = round($chance, 3) * 1000;
    $accum = $skin['chance'] += $accum;
  }

  $rand_num = rand(0, end($items)['chance']);

  $rand_element = getClosest($rand_num, $items);
  return $rand_element;
}

Пример:

$items = [
    ['id' => 1, 'cost' => 50],
    ['id' => 2, 'cost' => 100],
    ['id' => 3, 'cost' => 10],
    ['id' => 4, 'cost' => 200],
];

$open_cost = 50;

Исходя из этих данных шансы на выпадения предметов должны быть такими:

Формула - $item['cost'] / $open_cost

id_1 - 1
id_2 - 2
id_3 - 5
id_4 - 4

Чем меньше число тем больше шанс на выпадение.

Ответы

▲ 0Принят

Playground: https://onlinephp.io/c/d9e8b , можете покрутить и увидеть результат.

Прошу учесть, что код рабочий, однако концептуальный, детали, вроде деления на 0 и прочего не обрабатывалось, возложим это на вас)

Код:

<?php

$items = [
    ['id' => 1, 'cost' => 50],
    ['id' => 1, 'cost' => 51],
    ['id' => 1, 'cost' => 53],
    ['id' => 1, 'cost' => 55],
    ['id' => 1, 'cost' => 35],
    ['id' => 1, 'cost' => 37],
    ['id' => 1, 'cost' => 40],
    ['id' => 1, 'cost' => 43],
    ['id' => 1, 'cost' => 60],
    ['id' => 1, 'cost' => 62],
    ['id' => 1, 'cost' => 67],
    ['id' => 2, 'cost' => 100],
    ['id' => 2, 'cost' => 127],
    ['id' => 3, 'cost' => 10],
    ['id' => 4, 'cost' => 200],
];

// стоимость открытия ящика
$openCost       = 50;
// погрешность стоимости 50 + (50 * 0.2 = 10) = 60 это макс, мин будет на 10 меньше, за границы не уходим
$factor         = 0.2; // env перменная
$factorValue    = $openCost * $factor;
$minItemCost    = $openCost - $factorValue; // 40
$maxItemCost    = $openCost + $factorValue; // 60
// коэффициент выпадения элементов стоимостью больше, стоимости открытия
$coeff          = 0.90; // env переменная, случайность выпадения будет примерно 45% больше стоимости к 55% меньше стоимости
// эти значения должны сохраняться в базу как счетчик, либо же ходить в нее и аггрегировать данные значения на каждое открытие
$moreCount      = 5; // это счетчик, либо аггрегированное значение кол-ва выпадений больше стоимости
$lessCount      = 5; // это счетчик, либо аггрегированное значение кол-ва выпадений меньше стоимости
// это переменная фарта, т.е. сколько итераций пользователю нужно прокрутиться, чтобы выиграть элемент больше стоимости открытия
$fart           = 15;

$targetItemsSlice = array_values(
    array_filter(
        $items,
        static function ($item) use ($minItemCost, $maxItemCost, &$moreCount, &$lessCount) {
            return $item['cost'] >= $minItemCost && $item['cost'] <= $maxItemCost;
        }
    )
);

// чтобы не залипнуть на вечно в цикле, на всякий случай
$threshold = 1000; // env переменная
$curFart   = 0; // тот самый счетчик удачи
for ($i = 0; $i <= $threshold; $i++) {
    $targetItem = $targetItemsSlice[rand(0, count($targetItemsSlice) - 1)];
    
    // var_dump($targetItem);

    if ($targetItem['cost'] > $openCost) {
        $moreCount = $moreCount + 1; // либо же пишем в базу и заного аггрегируем, чтобы узнать актуальные данные
    } elseif ($targetItem['cost'] < $openCost) {
        $lessCount = $lessCount + 1; // либо же пишем в базу и заного аггрегируем, чтобы узнать актуальные данные 
    }
    
    // var_dump($moreCount,$lessCount); die;

    // тут считаем коэффициент выпадений, больше стоимости открытия к меньше стоимости открытия
    $curCoeff = $moreCount / $lessCount;

    // если результат выполнения данного условия true, значит больше стоимости выпадает слишком часто
    if ($curCoeff >= $coeff) {
        if ($curFart >= $fart) {
            // юзер выиграл айтем больше стоимости 
            var_dump(sprintf('Ваш выигрыш%s, поздравляем - ' . $targetItem['cost'], $targetItem['cost'] > $openCost ? ' больше стоимости открытия' : ''));

            break;
        }
        
        $curFart++;
        continue;
    }

    var_dump('Ваш выигрыш, поздравляем - ' . $targetItem['cost']);

    break;
}

var_dump('The end.');
▲ 0

Можно добиться такого поведения через обычную сортировку с callback-ом.

<?php

$openCost = 50;

$items = [
    ['id' => 1, 'cost' => 50],
    ['id' => 1, 'cost' => 55],
    ['id' => 1, 'cost' => 35],
    ['id' => 1, 'cost' => 40],
    ['id' => 1, 'cost' => 60],
    ['id' => 2, 'cost' => 100],
    ['id' => 3, 'cost' => 10],
    ['id' => 4, 'cost' => 200],
];

usort(
    $items,
    static function ($a, $b) use ($openCost) {
        $distA = ($previous - $a['cost']) < 0
            ? $a['cost'] - $openCost
            : $openCost - $a['cost'];

        $distB = ($previous - $b['cost']) < 0
            ? $b['cost'] - $openCost
            : $openCost - $b['cost'];

        
        return $distA === $distB
            ? 0
            : (
                $distA < $distB
                    ? -1
                    : 1
            );
    }
);

foreach($items as $item) {
    var_dump($item['cost']);
}

Ответ:

int(50)
int(55)
int(40)
int(60)
int(35)
int(10)
int(100)
int(200)