Laravel 11中如何使用Seeder与Factory实现从固定数量的已有用户中随机生成符合2v2规则的游戏关联
问题描述
我的问题和一个未解决的问题类似。我正在用Laravel 11做一个2v2天梯系统,有两个模型:
- Users(用户)
- Games(游戏)
用户和游戏之间有一个中间表,里面加了user_team字段,用来定义用户属于哪个队伍。
用户可以参与无限多场游戏,但每场游戏只能有4个玩家,每个玩家的队伍由中间表的字段决定。
我现在的Seeder代码是这样的:
$users = User::factory(20)->create(); Game::factory(100) ->hasAttached($users->random(2), [ 'user_team' => 'team1' ]) ->hasAttached($users->random(2), [ 'user_team' => 'team2' ]) ->create();
这段代码能生成20个用户和100场游戏,但问题是每场游戏都用的是同样的4个用户。如果我直接把User::factory()放进hasAttached()里,又会给每场游戏都生成新用户。
我想要的效果是:用这20个用户随机组队参与100场游戏,而且同一场游戏里不能有重复的用户。
希望我表达清楚了需求!
解决方案:自定义逻辑实现随机不重复组队
这个问题我之前做类似的多人对战系统时也踩过坑,核心问题是你两次调用$users->random(2)时,都是从整个用户池里随机抽取,不仅可能出现重复用户,还会导致每场游戏的玩家固定。咱们可以通过自定义Factory回调或者Seeder里的循环逻辑来解决:
方法一:直接在Seeder里处理(快速实现)
修改你的Seeder代码,先为每场游戏一次性选出4个不重复的用户,再拆分两队关联:
$users = User::factory(20)->create(); // 遍历每个生成的游戏,单独处理用户关联 Game::factory(100)->create()->each(function ($game) use ($users) { // 从20个用户里随机选4个完全不重复的 $selectedPlayers = $users->random(4); // 拆分两队:前2个是team1,后2个是team2 $team1 = $selectedPlayers->take(2); $team2 = $selectedPlayers->skip(2); // 关联team1用户到游戏,设置队伍字段 $game->users()->attach( $team1, array_fill(0, $team1->count(), ['user_team' => 'team1']) ); // 关联team2用户到游戏 $game->users()->attach( $team2, array_fill(0, $team2->count(), ['user_team' => 'team2']) ); });
方法二:封装成Factory状态(更优雅,可复用)
如果以后需要多次生成2v2的游戏数据,可以把这个逻辑封装到Game Factory的状态里,方便复用:
在database/factories/GameFactory.php中添加状态方法:
use Illuminate\Database\Eloquent\Collection; // ... public function twoVsTwo(Collection $users) { return $this->afterCreating(function ($game) use ($users) { // 随机选4个不重复用户 $selectedPlayers = $users->random(4); $team1 = $selectedPlayers->take(2); $team2 = $selectedPlayers->skip(2); // 关联两队用户 $game->users()->attach($team1, ['user_team' => 'team1']); $game->users()->attach($team2, ['user_team' => 'team2']); }); }
然后在Seeder里调用这个状态:
$users = User::factory(20)->create(); Game::factory(100) ->twoVsTwo($users) ->create();
方案优势说明
- 先一次性抽取4个不重复用户,彻底避免了同一场游戏出现重复玩家的问题
- 所有游戏都从已有的20个用户中随机选择,不会生成新用户,也不会固定使用某几个用户
- 两种方法可以根据你的需求选择:快速实现用方法一,需要复用逻辑用方法二
如果想要更贴近真实场景的对战数据(比如让部分用户参与更多游戏),还可以用weightedRandom方法给用户设置权重,但基础的2v2不重复组队需求,上面的方案完全够用啦。
备注:内容来源于stack exchange,提问作者Ricaz

