Search code examples
laraveleloquentfactorylaravel-factory

How to do unique combinatorics with Laravel Factory


I am trying to make a browser game in Laravel, and having some trouble with understanding the documentation for factories.

So this is the situation, each hero has possible 5 skills. But the hero will be only assigned randomly 3 of the 5 skills.

1,2,3
1,2,4
1,2,5
2,3,4
2,3,5
3,4,5

Instead of duplicating the factory 6 times is there a way for it to create these unique combos?

Hero::factory()->create([
    'hero_class' => 'Warrior',
    'attribute_health' => 100,
    'attribute_attack' => 35,
    'attribute_armor' => 75,
    'attribute_speed' => 22,
    'attribute_crit' => 15,
    'skill_1' => 1,
    'skill_2' => 1,
    'skill_3' => 1,
]);

I saw in docs you can do sequences

$users = User::factory()
                ->count(10)
                ->state(new Sequence(
                    ['admin' => 'Y'],
                    ['admin' => 'N'],
                ))
                ->create();

However, is there some more dynamic way in case I have more skill combos and don't want to manually type out 50 arrays?

To do the simple combos I had to do this

Hero::factory(5)
          ->state(new Sequence(
              [ 'skill_1' => 1, 'skill_2' => 2, 'skill_3' => 3, ],
              [ 'skill_1' => 1, 'skill_2' => 2, 'skill_3' => 4, ],
              [ 'skill_1' => 1, 'skill_2' => 2, 'skill_3' => 5, ],
              [ 'skill_1' => 2, 'skill_2' => 3, 'skill_3' => 4, ],
              [ 'skill_1' => 2, 'skill_2' => 3, 'skill_3' => 5, ],
              [ 'skill_1' => 3, 'skill_2' => 4, 'skill_3' => 5, ],
          ))
          ->create([
          'hero_class' => 'Warrior',
          'attribute_health' => 100,
          'attribute_attack' => 35,
          'attribute_armor' => 75,
          'attribute_speed' => 22,
          'attribute_crit' => 15,
      ]);

Any insight on how to go about this is much appreciated ;)


Solution

  • What you could do, is first create some random, unique combinations of skills in an array:

    $skills = [];
    do { 
        $randomSkillSet = [
            'skill_1' => rand(1, 5), 
            'skill_2' => rand(1, 5), 
            'skill_3' => rand(1, 5)
        ];
    
        if (!in_array($randomSkillSet, $skills)) {
            $skills[] = $randomSkillSet;
        }
    } while (count($skills) < 50);
    // We make sure 50 is less than 5^3 = 125
    

    Now you can use this array with unique skillsets in your Sequence, by taking each item from the $skills array in a Sequence closure, like this:

    use Illuminate\Database\Eloquent\Factories\Sequence;
    
    Hero::factory(50)
        ->state(new Sequence(
            function ($sequence) use ($skills) {
                return $skills[$sequence->index];
            }
        ))
     ->create([
        'hero_class' => 'Warrior',
        'attribute_health' => 100,
        'attribute_attack' => 35,
        'attribute_armor' => 75,
        'attribute_speed' => 22,
        'attribute_crit' => 15,
    ]);
    

    This is possible because we can use the index property of the sequence instance that is injected into the closure.